backtest-kit 6.14.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",
@@ -7963,6 +7991,40 @@ class ClientStrategy {
7963
7991
  }
7964
7992
  return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
7965
7993
  }
7994
+ /**
7995
+ * Returns the number of minutes the position has been active since it opened.
7996
+ *
7997
+ * Computed as elapsed minutes since `pendingAt` (the moment the signal was activated).
7998
+ * Returns null if no pending signal exists.
7999
+ *
8000
+ * @param symbol - Trading pair symbol
8001
+ * @param timestamp - Current Unix timestamp in milliseconds
8002
+ * @returns Promise resolving to active minutes (≥ 0) or null
8003
+ */
8004
+ async getPositionActiveMinutes(symbol, timestamp) {
8005
+ this.params.logger.debug("ClientStrategy getPositionActiveMinutes", { symbol });
8006
+ if (!this._pendingSignal) {
8007
+ return null;
8008
+ }
8009
+ return Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
8010
+ }
8011
+ /**
8012
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
8013
+ *
8014
+ * Computed as elapsed minutes since `scheduledAt` (the moment the scheduled signal was created).
8015
+ * Returns null if no scheduled signal exists.
8016
+ *
8017
+ * @param symbol - Trading pair symbol
8018
+ * @param timestamp - Current Unix timestamp in milliseconds
8019
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
8020
+ */
8021
+ async getPositionWaitingMinutes(symbol, timestamp) {
8022
+ this.params.logger.debug("ClientStrategy getPositionWaitingMinutes", { symbol });
8023
+ if (!this._scheduledSignal) {
8024
+ return null;
8025
+ }
8026
+ return Math.floor((timestamp - this._scheduledSignal.scheduledAt) / 60000);
8027
+ }
7966
8028
  /**
7967
8029
  * Returns the number of minutes elapsed since the highest profit price was recorded.
7968
8030
  *
@@ -10321,11 +10383,44 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => trycatch(async (symbol, strateg
10321
10383
  },
10322
10384
  defaultValue: null,
10323
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
+ });
10324
10420
  /**
10325
10421
  * Creates a callback function for emitting active ping events.
10326
10422
  *
10327
10423
  * Called by ClientStrategy when an active pending signal is being monitored every minute.
10328
- * Placeholder for future activePingSubject implementation.
10329
10424
  *
10330
10425
  * @param self - Reference to StrategyConnectionService instance
10331
10426
  * @returns Callback function for active ping events
@@ -10572,6 +10667,7 @@ class StrategyConnectionService {
10572
10667
  onInit: CREATE_COMMIT_INIT_FN(this),
10573
10668
  onSchedulePing: CREATE_COMMIT_SCHEDULE_PING_FN(this),
10574
10669
  onActivePing: CREATE_COMMIT_ACTIVE_PING_FN(this),
10670
+ onIdlePing: CREATE_COMMIT_IDLE_PING_FN(this),
10575
10671
  onDispose: CREATE_COMMIT_DISPOSE_FN(this),
10576
10672
  onCommit: CREATE_COMMIT_FN(this),
10577
10673
  onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
@@ -11064,6 +11160,46 @@ class StrategyConnectionService {
11064
11160
  const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11065
11161
  return await strategy.getPositionCountdownMinutes(symbol, timestamp);
11066
11162
  };
11163
+ /**
11164
+ * Returns the number of minutes the position has been active since it opened.
11165
+ *
11166
+ * Delegates to ClientStrategy.getPositionActiveMinutes().
11167
+ * Returns null if no pending signal exists.
11168
+ *
11169
+ * @param backtest - Whether running in backtest mode
11170
+ * @param symbol - Trading pair symbol
11171
+ * @param context - Execution context with strategyName, exchangeName, frameName
11172
+ * @returns Promise resolving to active minutes (≥ 0) or null
11173
+ */
11174
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
11175
+ this.loggerService.log("strategyConnectionService getPositionActiveMinutes", {
11176
+ symbol,
11177
+ context,
11178
+ });
11179
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11180
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11181
+ return await strategy.getPositionActiveMinutes(symbol, timestamp);
11182
+ };
11183
+ /**
11184
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
11185
+ *
11186
+ * Delegates to ClientStrategy.getPositionWaitingMinutes().
11187
+ * Returns null if no scheduled signal exists.
11188
+ *
11189
+ * @param backtest - Whether running in backtest mode
11190
+ * @param symbol - Trading pair symbol
11191
+ * @param context - Execution context with strategyName, exchangeName, frameName
11192
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
11193
+ */
11194
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
11195
+ this.loggerService.log("strategyConnectionService getPositionWaitingMinutes", {
11196
+ symbol,
11197
+ context,
11198
+ });
11199
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11200
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11201
+ return await strategy.getPositionWaitingMinutes(symbol, timestamp);
11202
+ };
11067
11203
  /**
11068
11204
  * Returns the best price reached in the profit direction during this position's life.
11069
11205
  *
@@ -13010,6 +13146,34 @@ const CALL_PING_SCHEDULED_FN = trycatch(async (event, self) => {
13010
13146
  },
13011
13147
  defaultValue: null,
13012
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
+ });
13013
13177
  /**
13014
13178
  * Wrapper to call pingActive method with error capture.
13015
13179
  */
@@ -13248,6 +13412,18 @@ class ActionProxy {
13248
13412
  async pingActive(event) {
13249
13413
  return await CALL_PING_ACTIVE_FN(event, this);
13250
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
+ }
13251
13427
  /**
13252
13428
  * Handles risk rejection events with error capture.
13253
13429
  *
@@ -13435,6 +13611,23 @@ const CALL_PING_SCHEDULED_CALLBACK_FN = trycatch(async (self, event, strategyNam
13435
13611
  errorEmitter.next(error);
13436
13612
  },
13437
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
+ });
13438
13631
  /** Wrapper to call active ping callback with error handling */
13439
13632
  const CALL_PING_ACTIVE_CALLBACK_FN = trycatch(async (self, event, strategyName, frameName, backtest) => {
13440
13633
  if (self.params.callbacks?.onPingActive) {
@@ -13801,6 +13994,24 @@ class ClientAction {
13801
13994
  await CALL_PING_ACTIVE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
13802
13995
  }
13803
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
+ ;
13804
14015
  /**
13805
14016
  * Handles risk rejection events when signals fail risk validation.
13806
14017
  */
@@ -14053,6 +14264,21 @@ class ActionConnectionService {
14053
14264
  const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
14054
14265
  await action.pingActive(event);
14055
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
+ };
14056
14282
  /**
14057
14283
  * Routes riskRejection event to appropriate ClientAction instance.
14058
14284
  *
@@ -15384,6 +15610,38 @@ class StrategyCoreService {
15384
15610
  await this.validate(context);
15385
15611
  return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
15386
15612
  };
15613
+ /**
15614
+ * Returns the number of minutes the position has been active since it opened.
15615
+ *
15616
+ * @param backtest - Whether running in backtest mode
15617
+ * @param symbol - Trading pair symbol
15618
+ * @param context - Execution context with strategyName, exchangeName, frameName
15619
+ * @returns Promise resolving to active minutes (≥ 0) or null
15620
+ */
15621
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
15622
+ this.loggerService.log("strategyCoreService getPositionActiveMinutes", {
15623
+ symbol,
15624
+ context,
15625
+ });
15626
+ await this.validate(context);
15627
+ return await this.strategyConnectionService.getPositionActiveMinutes(backtest, symbol, context);
15628
+ };
15629
+ /**
15630
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
15631
+ *
15632
+ * @param backtest - Whether running in backtest mode
15633
+ * @param symbol - Trading pair symbol
15634
+ * @param context - Execution context with strategyName, exchangeName, frameName
15635
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
15636
+ */
15637
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
15638
+ this.loggerService.log("strategyCoreService getPositionWaitingMinutes", {
15639
+ symbol,
15640
+ context,
15641
+ });
15642
+ await this.validate(context);
15643
+ return await this.strategyConnectionService.getPositionWaitingMinutes(backtest, symbol, context);
15644
+ };
15387
15645
  /**
15388
15646
  * Returns the best price reached in the profit direction during this position's life.
15389
15647
  *
@@ -16124,6 +16382,27 @@ class ActionCoreService {
16124
16382
  await this.actionConnectionService.pingActive(event, backtest, { actionName, ...context });
16125
16383
  }
16126
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
+ };
16127
16406
  /**
16128
16407
  * Routes risk rejection event to all registered actions for the strategy.
16129
16408
  *
@@ -32390,7 +32669,7 @@ class NotificationHelperService {
32390
32669
  const pendingSignal = await this.strategyCoreService.getPendingSignal(backtest, symbol, currentPrice, {
32391
32670
  strategyName: context.strategyName,
32392
32671
  exchangeName: context.exchangeName,
32393
- frameName: "",
32672
+ frameName: context.frameName,
32394
32673
  });
32395
32674
  if (!pendingSignal) {
32396
32675
  throw new Error(`SignalUtils notify No pending signal found symbol=${symbol} `);
@@ -35530,6 +35809,8 @@ const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
35530
35809
  const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
35531
35810
  const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
35532
35811
  const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
35812
+ const GET_POSITION_ACTIVE_MINUTES_METHOD_NAME = "strategy.getPositionActiveMinutes";
35813
+ const GET_POSITION_WAITING_MINUTES_METHOD_NAME = "strategy.getPositionWaitingMinutes";
35533
35814
  const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
35534
35815
  const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
35535
35816
  const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
@@ -36830,6 +37111,62 @@ async function getPositionCountdownMinutes(symbol) {
36830
37111
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36831
37112
  return await backtest.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
36832
37113
  }
37114
+ /**
37115
+ * Returns the number of minutes the position has been active since it opened.
37116
+ *
37117
+ * Returns null if no pending signal exists.
37118
+ *
37119
+ * @param symbol - Trading pair symbol
37120
+ * @returns Promise resolving to active minutes (≥ 0) or null
37121
+ *
37122
+ * @example
37123
+ * ```typescript
37124
+ * import { getPositionActiveMinutes } from "backtest-kit";
37125
+ *
37126
+ * const minutes = await getPositionActiveMinutes("BTCUSDT");
37127
+ * // e.g. 120 (position has been open for 2 hours)
37128
+ * ```
37129
+ */
37130
+ async function getPositionActiveMinutes(symbol) {
37131
+ backtest.loggerService.info(GET_POSITION_ACTIVE_MINUTES_METHOD_NAME, { symbol });
37132
+ if (!ExecutionContextService.hasContext()) {
37133
+ throw new Error("getPositionActiveMinutes requires an execution context");
37134
+ }
37135
+ if (!MethodContextService.hasContext()) {
37136
+ throw new Error("getPositionActiveMinutes requires a method context");
37137
+ }
37138
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37139
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37140
+ return await backtest.strategyCoreService.getPositionActiveMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
37141
+ }
37142
+ /**
37143
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
37144
+ *
37145
+ * Returns null if no scheduled signal exists.
37146
+ *
37147
+ * @param symbol - Trading pair symbol
37148
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
37149
+ *
37150
+ * @example
37151
+ * ```typescript
37152
+ * import { getPositionWaitingMinutes } from "backtest-kit";
37153
+ *
37154
+ * const minutes = await getPositionWaitingMinutes("BTCUSDT");
37155
+ * // e.g. 15 (scheduled signal has been waiting 15 minutes for activation)
37156
+ * ```
37157
+ */
37158
+ async function getPositionWaitingMinutes(symbol) {
37159
+ backtest.loggerService.info(GET_POSITION_WAITING_MINUTES_METHOD_NAME, { symbol });
37160
+ if (!ExecutionContextService.hasContext()) {
37161
+ throw new Error("getPositionWaitingMinutes requires an execution context");
37162
+ }
37163
+ if (!MethodContextService.hasContext()) {
37164
+ throw new Error("getPositionWaitingMinutes requires a method context");
37165
+ }
37166
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37167
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37168
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
37169
+ }
36833
37170
  /**
36834
37171
  * Returns the best price reached in the profit direction during this position's life.
36835
37172
  *
@@ -37636,6 +37973,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
37636
37973
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
37637
37974
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
37638
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";
37639
37978
  const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
37640
37979
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
37641
37980
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
@@ -38811,6 +39150,45 @@ function listenActivePingOnce(filterFn, fn) {
38811
39150
  };
38812
39151
  return disposeFn = listenActivePing(wrappedFn);
38813
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
+ }
38814
39192
  /**
38815
39193
  * Subscribes to strategy management events with queued async processing.
38816
39194
  *
@@ -39100,6 +39478,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPar
39100
39478
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
39101
39479
  const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
39102
39480
  const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
39481
+ const BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "BacktestUtils.getPositionActiveMinutes";
39482
+ const BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "BacktestUtils.getPositionWaitingMinutes";
39103
39483
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
39104
39484
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
39105
39485
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
@@ -40055,6 +40435,60 @@ class BacktestUtils {
40055
40435
  }
40056
40436
  return await backtest.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
40057
40437
  };
40438
+ /**
40439
+ * Returns the number of minutes the position has been active since it opened.
40440
+ *
40441
+ * Returns null if no pending signal exists.
40442
+ *
40443
+ * @param symbol - Trading pair symbol
40444
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40445
+ * @returns Active minutes (≥ 0), or null if no active position
40446
+ */
40447
+ this.getPositionActiveMinutes = async (symbol, context) => {
40448
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
40449
+ symbol,
40450
+ context,
40451
+ });
40452
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40453
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40454
+ {
40455
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40456
+ riskName &&
40457
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40458
+ riskList &&
40459
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40460
+ actions &&
40461
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40462
+ }
40463
+ return await backtest.strategyCoreService.getPositionActiveMinutes(true, symbol, context);
40464
+ };
40465
+ /**
40466
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
40467
+ *
40468
+ * Returns null if no scheduled signal exists.
40469
+ *
40470
+ * @param symbol - Trading pair symbol
40471
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40472
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
40473
+ */
40474
+ this.getPositionWaitingMinutes = async (symbol, context) => {
40475
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
40476
+ symbol,
40477
+ context,
40478
+ });
40479
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40480
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40481
+ {
40482
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40483
+ riskName &&
40484
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40485
+ riskList &&
40486
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40487
+ actions &&
40488
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40489
+ }
40490
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(true, symbol, context);
40491
+ };
40058
40492
  /**
40059
40493
  * Returns the best price reached in the profit direction during this position's life.
40060
40494
  *
@@ -41696,6 +42130,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
41696
42130
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
41697
42131
  const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
41698
42132
  const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
42133
+ const LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "LiveUtils.getPositionActiveMinutes";
42134
+ const LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "LiveUtils.getPositionWaitingMinutes";
41699
42135
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
41700
42136
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
41701
42137
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
@@ -42730,6 +43166,68 @@ class LiveUtils {
42730
43166
  frameName: "",
42731
43167
  });
42732
43168
  };
43169
+ /**
43170
+ * Returns the number of minutes the position has been active since it opened.
43171
+ *
43172
+ * Returns null if no pending signal exists.
43173
+ *
43174
+ * @param symbol - Trading pair symbol
43175
+ * @param context - Execution context with strategyName and exchangeName
43176
+ * @returns Active minutes (≥ 0), or null if no active position
43177
+ */
43178
+ this.getPositionActiveMinutes = async (symbol, context) => {
43179
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
43180
+ symbol,
43181
+ context,
43182
+ });
43183
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
43184
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
43185
+ {
43186
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43187
+ riskName &&
43188
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
43189
+ riskList &&
43190
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
43191
+ actions &&
43192
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
43193
+ }
43194
+ return await backtest.strategyCoreService.getPositionActiveMinutes(false, symbol, {
43195
+ strategyName: context.strategyName,
43196
+ exchangeName: context.exchangeName,
43197
+ frameName: "",
43198
+ });
43199
+ };
43200
+ /**
43201
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
43202
+ *
43203
+ * Returns null if no scheduled signal exists.
43204
+ *
43205
+ * @param symbol - Trading pair symbol
43206
+ * @param context - Execution context with strategyName and exchangeName
43207
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
43208
+ */
43209
+ this.getPositionWaitingMinutes = async (symbol, context) => {
43210
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
43211
+ symbol,
43212
+ context,
43213
+ });
43214
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43215
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43216
+ {
43217
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43218
+ riskName &&
43219
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43220
+ riskList &&
43221
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43222
+ actions &&
43223
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43224
+ }
43225
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(false, symbol, {
43226
+ strategyName: context.strategyName,
43227
+ exchangeName: context.exchangeName,
43228
+ frameName: "",
43229
+ });
43230
+ };
42733
43231
  /**
42734
43232
  * Returns the best price reached in the profit direction during this position's life.
42735
43233
  *
@@ -45648,6 +46146,7 @@ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45648
46146
  const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45649
46147
  const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45650
46148
  const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
46149
+ const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapter.getMinutesSinceLatestSignalCreated";
45651
46150
  /**
45652
46151
  * Builds a composite storage key from context parts.
45653
46152
  * Includes backtest flag as the last segment to prevent live/backtest collisions.
@@ -45706,6 +46205,23 @@ class RecentPersistBacktestUtils {
45706
46205
  });
45707
46206
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45708
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
+ };
45709
46225
  }
45710
46226
  }
45711
46227
  /**
@@ -45748,6 +46264,23 @@ class RecentMemoryBacktestUtils {
45748
46264
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45749
46265
  return this._signals.get(key) ?? null;
45750
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
+ };
45751
46284
  }
45752
46285
  }
45753
46286
  /**
@@ -45791,6 +46324,23 @@ class RecentPersistLiveUtils {
45791
46324
  });
45792
46325
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45793
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
+ };
45794
46344
  }
45795
46345
  }
45796
46346
  /**
@@ -45833,6 +46383,23 @@ class RecentMemoryLiveUtils {
45833
46383
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45834
46384
  return this._signals.get(key) ?? null;
45835
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
+ };
45836
46403
  }
45837
46404
  }
45838
46405
  /**
@@ -45879,6 +46446,32 @@ class RecentBacktestAdapter {
45879
46446
  });
45880
46447
  return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45881
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
+ };
45882
46475
  /**
45883
46476
  * Sets the storage adapter constructor.
45884
46477
  * All future storage operations will use this adapter.
@@ -45957,6 +46550,32 @@ class RecentLiveAdapter {
45957
46550
  });
45958
46551
  return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45959
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
+ };
45960
46579
  /**
45961
46580
  * Sets the storage adapter constructor.
45962
46581
  * All future storage operations will use this adapter.
@@ -46065,6 +46684,27 @@ class RecentAdapter {
46065
46684
  }
46066
46685
  return null;
46067
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
+ };
46068
46708
  }
46069
46709
  }
46070
46710
  /**
@@ -46084,6 +46724,7 @@ const RecentLive = new RecentLiveAdapter();
46084
46724
  const RecentBacktest = new RecentBacktestAdapter();
46085
46725
 
46086
46726
  const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
46727
+ const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
46087
46728
  /**
46088
46729
  * Returns the latest signal (pending or closed) for the current strategy context.
46089
46730
  *
@@ -46121,6 +46762,42 @@ async function getLatestSignal(symbol) {
46121
46762
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46122
46763
  return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
46123
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
+ }
46124
46801
 
46125
46802
  const DEFAULT_BM25_K1 = 1.5;
46126
46803
  const DEFAULT_BM25_B = 0.75;
@@ -48183,7 +48860,7 @@ const LOGGER_SERVICE$2 = new LoggerService();
48183
48860
  * Default configuration that enables all report services.
48184
48861
  * Used when no specific configuration is provided to enable().
48185
48862
  */
48186
- const WILDCARD_TARGET$1 = {
48863
+ const WILDCARD_TARGET$2 = {
48187
48864
  backtest: true,
48188
48865
  strategy: true,
48189
48866
  breakeven: true,
@@ -48234,7 +48911,7 @@ class ReportUtils {
48234
48911
  *
48235
48912
  * @returns Cleanup function that unsubscribes from all enabled services
48236
48913
  */
48237
- this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48914
+ this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$2) => {
48238
48915
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
48239
48916
  backtest: bt,
48240
48917
  breakeven,
@@ -48326,7 +49003,7 @@ class ReportUtils {
48326
49003
  * Report.disable();
48327
49004
  * ```
48328
49005
  */
48329
- this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
49006
+ this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$2) => {
48330
49007
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
48331
49008
  backtest: bt,
48332
49009
  breakeven,
@@ -48450,7 +49127,7 @@ const LOGGER_SERVICE$1 = new LoggerService();
48450
49127
  * Default configuration that enables all markdown services.
48451
49128
  * Used when no specific configuration is provided to `enable()`.
48452
49129
  */
48453
- const WILDCARD_TARGET = {
49130
+ const WILDCARD_TARGET$1 = {
48454
49131
  backtest: true,
48455
49132
  breakeven: true,
48456
49133
  heat: true,
@@ -48501,7 +49178,7 @@ class MarkdownUtils {
48501
49178
  *
48502
49179
  * @returns Cleanup function that unsubscribes from all enabled services
48503
49180
  */
48504
- this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET) => {
49181
+ this.enable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48505
49182
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_ENABLE, {
48506
49183
  backtest: bt,
48507
49184
  breakeven,
@@ -48595,7 +49272,7 @@ class MarkdownUtils {
48595
49272
  * Markdown.disable();
48596
49273
  * ```
48597
49274
  */
48598
- this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET) => {
49275
+ this.disable = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48599
49276
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_DISABLE, {
48600
49277
  backtest: bt,
48601
49278
  breakeven,
@@ -48676,7 +49353,7 @@ class MarkdownUtils {
48676
49353
  * @param config.highest_profit - Clear highest profit report data
48677
49354
  * @param config.max_drawdown - Clear max drawdown report data
48678
49355
  */
48679
- this.clear = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET) => {
49356
+ this.clear = ({ backtest: bt = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, max_drawdown = false, } = WILDCARD_TARGET$1) => {
48680
49357
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_CLEAR, {
48681
49358
  backtest: bt,
48682
49359
  breakeven,
@@ -49448,6 +50125,7 @@ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49448
50125
  /** List of all global subjects whose listeners should be snapshotted for session isolation */
49449
50126
  const SUBJECT_ISOLATION_LIST = [
49450
50127
  activePingSubject,
50128
+ idlePingSubject,
49451
50129
  backtestScheduleOpenSubject,
49452
50130
  breakevenSubject,
49453
50131
  doneBacktestSubject,
@@ -51088,6 +51766,8 @@ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.
51088
51766
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
51089
51767
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
51090
51768
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
51769
+ const REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "ReflectUtils.getPositionActiveMinutes";
51770
+ const REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "ReflectUtils.getPositionWaitingMinutes";
51091
51771
  const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
51092
51772
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
51093
51773
  const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
@@ -51362,6 +52042,52 @@ class ReflectUtils {
51362
52042
  }
51363
52043
  return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
51364
52044
  };
52045
+ /**
52046
+ * Returns the number of minutes the position has been active since it opened.
52047
+ *
52048
+ * Returns null if no pending signal exists.
52049
+ *
52050
+ * @param symbol - Trading pair symbol
52051
+ * @param context - Execution context with strategyName, exchangeName and frameName
52052
+ * @param backtest - True if backtest mode, false if live mode (default: false)
52053
+ * @returns Promise resolving to active minutes (≥ 0) or null
52054
+ */
52055
+ this.getPositionActiveMinutes = async (symbol, context, backtest$1 = false) => {
52056
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, { symbol, context });
52057
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52058
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52059
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52060
+ {
52061
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
52062
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52063
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
52064
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
52065
+ }
52066
+ return await backtest.strategyCoreService.getPositionActiveMinutes(backtest$1, symbol, context);
52067
+ };
52068
+ /**
52069
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
52070
+ *
52071
+ * Returns null if no scheduled signal exists.
52072
+ *
52073
+ * @param symbol - Trading pair symbol
52074
+ * @param context - Execution context with strategyName, exchangeName and frameName
52075
+ * @param backtest - True if backtest mode, false if live mode (default: false)
52076
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
52077
+ */
52078
+ this.getPositionWaitingMinutes = async (symbol, context, backtest$1 = false) => {
52079
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES, { symbol, context });
52080
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52081
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52082
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52083
+ {
52084
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
52085
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52086
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
52087
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
52088
+ }
52089
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(backtest$1, symbol, context);
52090
+ };
51365
52091
  /**
51366
52092
  * Returns the number of minutes elapsed since the highest profit price was recorded.
51367
52093
  *
@@ -53638,6 +54364,23 @@ const StorageLive = new StorageLiveAdapter();
53638
54364
  */
53639
54365
  const StorageBacktest = new StorageBacktestAdapter();
53640
54366
 
54367
+ /**
54368
+ * Default configuration that enables all notification types.
54369
+ * Used when no specific configuration is provided to enable().
54370
+ */
54371
+ const WILDCARD_TARGET = {
54372
+ signal: true,
54373
+ partial_profit: true,
54374
+ partial_loss: true,
54375
+ breakeven: true,
54376
+ strategy_commit: true,
54377
+ signal_sync: true,
54378
+ risk: true,
54379
+ info: true,
54380
+ common_error: true,
54381
+ critical_error: true,
54382
+ validation_error: true,
54383
+ };
53641
54384
  /**
53642
54385
  * Generates a unique key for notification identification.
53643
54386
  * @returns Random string identifier
@@ -55693,64 +56436,152 @@ class NotificationAdapter {
55693
56436
  *
55694
56437
  * @returns Cleanup function that unsubscribes from all emitters
55695
56438
  */
55696
- this.enable = singleshot(() => {
56439
+ this.enable = singleshot(({ signal = false, info = false, partial_profit = false, partial_loss = false, breakeven = false, strategy_commit = false, signal_sync = false, risk = false, common_error = false, critical_error = false, validation_error = false, } = WILDCARD_TARGET) => {
55697
56440
  backtest.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_ENABLE);
55698
56441
  let unLive;
55699
56442
  let unBacktest;
55700
56443
  {
55701
- const unBacktestSignal = signalBacktestEmitter.subscribe((data) => NotificationBacktest.handleSignal(data));
56444
+ const unBacktestSignal = signalBacktestEmitter.subscribe(async (data) => {
56445
+ if (signal) {
56446
+ await NotificationBacktest.handleSignal(data);
56447
+ }
56448
+ });
55702
56449
  const unBacktestPartialProfit = partialProfitSubject
55703
56450
  .filter(({ backtest }) => backtest)
55704
- .connect((data) => NotificationBacktest.handlePartialProfit(data));
56451
+ .connect(async (data) => {
56452
+ if (partial_profit) {
56453
+ await NotificationBacktest.handlePartialProfit(data);
56454
+ }
56455
+ });
55705
56456
  const unBacktestPartialLoss = partialLossSubject
55706
56457
  .filter(({ backtest }) => backtest)
55707
- .connect((data) => NotificationBacktest.handlePartialLoss(data));
56458
+ .connect(async (data) => {
56459
+ if (partial_loss) {
56460
+ await NotificationBacktest.handlePartialLoss(data);
56461
+ }
56462
+ });
55708
56463
  const unBacktestBreakeven = breakevenSubject
55709
56464
  .filter(({ backtest }) => backtest)
55710
- .connect((data) => NotificationBacktest.handleBreakeven(data));
56465
+ .connect(async (data) => {
56466
+ if (breakeven) {
56467
+ await NotificationBacktest.handleBreakeven(data);
56468
+ }
56469
+ });
55711
56470
  const unBacktestStrategyCommit = strategyCommitSubject
55712
56471
  .filter(({ backtest }) => backtest)
55713
- .connect((data) => NotificationBacktest.handleStrategyCommit(data));
56472
+ .connect(async (data) => {
56473
+ if (strategy_commit) {
56474
+ await NotificationBacktest.handleStrategyCommit(data);
56475
+ }
56476
+ });
55714
56477
  const unBacktestSync = syncSubject
55715
56478
  .filter(({ backtest }) => backtest)
55716
- .connect((data) => NotificationBacktest.handleSync(data));
56479
+ .connect(async (data) => {
56480
+ if (signal_sync) {
56481
+ await NotificationBacktest.handleSync(data);
56482
+ }
56483
+ });
55717
56484
  const unBacktestRisk = riskSubject
55718
56485
  .filter(({ backtest }) => backtest)
55719
- .connect((data) => NotificationBacktest.handleRisk(data));
55720
- const unBacktestError = errorEmitter.subscribe((error) => NotificationBacktest.handleError(error));
55721
- const unBacktestExit = exitEmitter.subscribe((error) => NotificationBacktest.handleCriticalError(error));
55722
- const unBacktestValidation = validationSubject.subscribe((error) => NotificationBacktest.handleValidationError(error));
56486
+ .connect(async (data) => {
56487
+ if (risk) {
56488
+ await NotificationBacktest.handleRisk(data);
56489
+ }
56490
+ });
56491
+ const unBacktestError = errorEmitter.subscribe(async (error) => {
56492
+ if (common_error) {
56493
+ await NotificationBacktest.handleError(error);
56494
+ }
56495
+ });
56496
+ const unBacktestExit = exitEmitter.subscribe(async (error) => {
56497
+ if (critical_error) {
56498
+ await NotificationBacktest.handleCriticalError(error);
56499
+ }
56500
+ });
56501
+ const unBacktestValidation = validationSubject.subscribe(async (error) => {
56502
+ if (validation_error) {
56503
+ await NotificationBacktest.handleValidationError(error);
56504
+ }
56505
+ });
55723
56506
  const unBacktestSignalNotify = signalNotifySubject
55724
56507
  .filter(({ backtest }) => backtest)
55725
- .connect((data) => NotificationBacktest.handleSignalNotify(data));
56508
+ .connect(async (data) => {
56509
+ if (info) {
56510
+ await NotificationBacktest.handleSignalNotify(data);
56511
+ }
56512
+ });
55726
56513
  unBacktest = compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation(), () => unBacktestSignalNotify());
55727
56514
  }
55728
56515
  {
55729
- const unLiveSignal = signalLiveEmitter.subscribe((data) => NotificationLive.handleSignal(data));
56516
+ const unLiveSignal = signalLiveEmitter.subscribe(async (data) => {
56517
+ if (signal) {
56518
+ await NotificationLive.handleSignal(data);
56519
+ }
56520
+ });
55730
56521
  const unLivePartialProfit = partialProfitSubject
55731
56522
  .filter(({ backtest }) => !backtest)
55732
- .connect((data) => NotificationLive.handlePartialProfit(data));
56523
+ .connect(async (data) => {
56524
+ if (partial_profit) {
56525
+ await NotificationLive.handlePartialProfit(data);
56526
+ }
56527
+ });
55733
56528
  const unLivePartialLoss = partialLossSubject
55734
56529
  .filter(({ backtest }) => !backtest)
55735
- .connect((data) => NotificationLive.handlePartialLoss(data));
56530
+ .connect(async (data) => {
56531
+ if (partial_loss) {
56532
+ await NotificationLive.handlePartialLoss(data);
56533
+ }
56534
+ });
55736
56535
  const unLiveBreakeven = breakevenSubject
55737
56536
  .filter(({ backtest }) => !backtest)
55738
- .connect((data) => NotificationLive.handleBreakeven(data));
56537
+ .connect(async (data) => {
56538
+ if (breakeven) {
56539
+ await NotificationLive.handleBreakeven(data);
56540
+ }
56541
+ });
55739
56542
  const unLiveStrategyCommit = strategyCommitSubject
55740
56543
  .filter(({ backtest }) => !backtest)
55741
- .connect((data) => NotificationLive.handleStrategyCommit(data));
56544
+ .connect(async (data) => {
56545
+ if (strategy_commit) {
56546
+ await NotificationLive.handleStrategyCommit(data);
56547
+ }
56548
+ });
55742
56549
  const unLiveSync = syncSubject
55743
56550
  .filter(({ backtest }) => !backtest)
55744
- .connect((data) => NotificationLive.handleSync(data));
56551
+ .connect(async (data) => {
56552
+ if (signal_sync) {
56553
+ await NotificationLive.handleSync(data);
56554
+ }
56555
+ });
55745
56556
  const unLiveRisk = riskSubject
55746
56557
  .filter(({ backtest }) => !backtest)
55747
- .connect((data) => NotificationLive.handleRisk(data));
55748
- const unLiveError = errorEmitter.subscribe((error) => NotificationLive.handleError(error));
55749
- const unLiveExit = exitEmitter.subscribe((error) => NotificationLive.handleCriticalError(error));
55750
- const unLiveValidation = validationSubject.subscribe((error) => NotificationLive.handleValidationError(error));
56558
+ .connect(async (data) => {
56559
+ if (risk) {
56560
+ await NotificationLive.handleRisk(data);
56561
+ }
56562
+ });
56563
+ const unLiveError = errorEmitter.subscribe(async (error) => {
56564
+ if (common_error) {
56565
+ await NotificationLive.handleError(error);
56566
+ }
56567
+ });
56568
+ const unLiveExit = exitEmitter.subscribe(async (error) => {
56569
+ if (critical_error) {
56570
+ await NotificationLive.handleCriticalError(error);
56571
+ }
56572
+ });
56573
+ const unLiveValidation = validationSubject.subscribe(async (error) => {
56574
+ if (validation_error) {
56575
+ await NotificationLive.handleValidationError(error);
56576
+ }
56577
+ });
55751
56578
  const unLiveSignalNotify = signalNotifySubject
55752
56579
  .filter(({ backtest }) => !backtest)
55753
- .connect((data) => NotificationLive.handleSignalNotify(data));
56580
+ .connect(async (data) => {
56581
+ if (info) {
56582
+ await NotificationLive.handleSignalNotify(data);
56583
+ }
56584
+ });
55754
56585
  unLive = compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation(), () => unLiveSignalNotify());
55755
56586
  }
55756
56587
  return () => {
@@ -57415,6 +58246,7 @@ const METHOD_NAME_PARTIAL_PROFIT_AVAILABLE = "ActionBase.partialProfitAvailable"
57415
58246
  const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
57416
58247
  const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
57417
58248
  const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
58249
+ const METHOD_NAME_PING_IDLE = "ActionBase.pingIdle";
57418
58250
  const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
57419
58251
  const METHOD_NAME_DISPOSE = "ActionBase.dispose";
57420
58252
  const DEFAULT_SOURCE = "default";
@@ -57808,6 +58640,26 @@ class ActionBase {
57808
58640
  source,
57809
58641
  });
57810
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
+ }
57811
58663
  /**
57812
58664
  * Handles risk rejection events when signals fail risk validation.
57813
58665
  *
@@ -58141,4 +58993,4 @@ const validateSignal = (signal, currentPrice) => {
58141
58993
  return !errors.length;
58142
58994
  };
58143
58995
 
58144
- 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, 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, 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 };