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.cjs CHANGED
@@ -734,6 +734,11 @@ const schedulePingSubject = new functoolsKit.Subject();
734
734
  * Allows users to track active signal lifecycle and implement custom dynamic management logic.
735
735
  */
736
736
  const activePingSubject = new functoolsKit.Subject();
737
+ /**
738
+ * Idle ping emitter for strategy idle state events.
739
+ * Emits every tick when there is no pending or scheduled signal being monitored.
740
+ */
741
+ const idlePingSubject = new functoolsKit.Subject();
737
742
  /**
738
743
  * Strategy management signal emitter.
739
744
  * Emits when strategy management actions are executed:
@@ -785,6 +790,7 @@ var emitters = /*#__PURE__*/Object.freeze({
785
790
  errorEmitter: errorEmitter,
786
791
  exitEmitter: exitEmitter,
787
792
  highestProfitSubject: highestProfitSubject,
793
+ idlePingSubject: idlePingSubject,
788
794
  maxDrawdownSubject: maxDrawdownSubject,
789
795
  partialLossSubject: partialLossSubject,
790
796
  partialProfitSubject: partialProfitSubject,
@@ -6048,6 +6054,27 @@ const CALL_ACTIVE_PING_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (sel
6048
6054
  errorEmitter.next(error);
6049
6055
  },
6050
6056
  });
6057
+ const CALL_IDLE_PING_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, timestamp, backtest, currentPrice) => {
6058
+ await ExecutionContextService.runInContext(async () => {
6059
+ // Call system onIdlePing callback (emits to idlePingSubject)
6060
+ 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);
6061
+ }, {
6062
+ when: new Date(timestamp),
6063
+ symbol: symbol,
6064
+ backtest: backtest,
6065
+ });
6066
+ }), {
6067
+ fallback: (error, self) => {
6068
+ const message = "ClientStrategy CALL_IDLE_PING_CALLBACKS_FN thrown";
6069
+ const payload = {
6070
+ error: functoolsKit.errorData(error),
6071
+ message: functoolsKit.getErrorMessage(error),
6072
+ };
6073
+ self.params.logger.warn(message, payload);
6074
+ console.warn(message, payload);
6075
+ errorEmitter.next(error);
6076
+ },
6077
+ });
6051
6078
  const CALL_ACTIVE_CALLBACKS_FN = functoolsKit.trycatch(beginTime(async (self, symbol, signal, currentPrice, timestamp, backtest) => {
6052
6079
  await ExecutionContextService.runInContext(async () => {
6053
6080
  if (self.params.callbacks?.onActive) {
@@ -6703,6 +6730,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backt
6703
6730
  };
6704
6731
  const RETURN_IDLE_FN = async (self, currentPrice) => {
6705
6732
  const currentTime = self.params.execution.context.when.getTime();
6733
+ await CALL_IDLE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, currentTime, self.params.execution.context.backtest, currentPrice);
6706
6734
  await CALL_IDLE_CALLBACKS_FN(self, self.params.execution.context.symbol, currentPrice, currentTime, self.params.execution.context.backtest);
6707
6735
  const result = {
6708
6736
  action: "idle",
@@ -7983,6 +8011,40 @@ class ClientStrategy {
7983
8011
  }
7984
8012
  return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
7985
8013
  }
8014
+ /**
8015
+ * Returns the number of minutes the position has been active since it opened.
8016
+ *
8017
+ * Computed as elapsed minutes since `pendingAt` (the moment the signal was activated).
8018
+ * Returns null if no pending signal exists.
8019
+ *
8020
+ * @param symbol - Trading pair symbol
8021
+ * @param timestamp - Current Unix timestamp in milliseconds
8022
+ * @returns Promise resolving to active minutes (≥ 0) or null
8023
+ */
8024
+ async getPositionActiveMinutes(symbol, timestamp) {
8025
+ this.params.logger.debug("ClientStrategy getPositionActiveMinutes", { symbol });
8026
+ if (!this._pendingSignal) {
8027
+ return null;
8028
+ }
8029
+ return Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
8030
+ }
8031
+ /**
8032
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
8033
+ *
8034
+ * Computed as elapsed minutes since `scheduledAt` (the moment the scheduled signal was created).
8035
+ * Returns null if no scheduled signal exists.
8036
+ *
8037
+ * @param symbol - Trading pair symbol
8038
+ * @param timestamp - Current Unix timestamp in milliseconds
8039
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
8040
+ */
8041
+ async getPositionWaitingMinutes(symbol, timestamp) {
8042
+ this.params.logger.debug("ClientStrategy getPositionWaitingMinutes", { symbol });
8043
+ if (!this._scheduledSignal) {
8044
+ return null;
8045
+ }
8046
+ return Math.floor((timestamp - this._scheduledSignal.scheduledAt) / 60000);
8047
+ }
7986
8048
  /**
7987
8049
  * Returns the number of minutes elapsed since the highest profit price was recorded.
7988
8050
  *
@@ -10341,11 +10403,44 @@ const CREATE_COMMIT_SCHEDULE_PING_FN = (self) => functoolsKit.trycatch(async (sy
10341
10403
  },
10342
10404
  defaultValue: null,
10343
10405
  });
10406
+ /**
10407
+ * Creates a callback function for emitting idle ping events.
10408
+ *
10409
+ * Called by ClientStrategy when no active or scheduled signals are present.
10410
+ *
10411
+ * @param self - Reference to StrategyConnectionService instance
10412
+ * @returns Callback function for idle ping events
10413
+ */
10414
+ const CREATE_COMMIT_IDLE_PING_FN = (self) => functoolsKit.trycatch(async (symbol, strategyName, exchangeName, currentPrice, backtest, timestamp) => {
10415
+ const frameName = self.methodContextService.context.frameName;
10416
+ const event = {
10417
+ symbol,
10418
+ strategyName,
10419
+ exchangeName,
10420
+ frameName,
10421
+ currentPrice,
10422
+ backtest,
10423
+ timestamp,
10424
+ };
10425
+ await idlePingSubject.next(event);
10426
+ await self.actionCoreService.pingIdle(backtest, event, { strategyName, exchangeName, frameName });
10427
+ }, {
10428
+ fallback: (error) => {
10429
+ const message = "StrategyConnectionService CREATE_COMMIT_IDLE_PING_FN thrown";
10430
+ const payload = {
10431
+ error: functoolsKit.errorData(error),
10432
+ message: functoolsKit.getErrorMessage(error),
10433
+ };
10434
+ self.loggerService.warn(message, payload);
10435
+ console.warn(message, payload);
10436
+ errorEmitter.next(error);
10437
+ },
10438
+ defaultValue: null,
10439
+ });
10344
10440
  /**
10345
10441
  * Creates a callback function for emitting active ping events.
10346
10442
  *
10347
10443
  * Called by ClientStrategy when an active pending signal is being monitored every minute.
10348
- * Placeholder for future activePingSubject implementation.
10349
10444
  *
10350
10445
  * @param self - Reference to StrategyConnectionService instance
10351
10446
  * @returns Callback function for active ping events
@@ -10592,6 +10687,7 @@ class StrategyConnectionService {
10592
10687
  onInit: CREATE_COMMIT_INIT_FN(this),
10593
10688
  onSchedulePing: CREATE_COMMIT_SCHEDULE_PING_FN(this),
10594
10689
  onActivePing: CREATE_COMMIT_ACTIVE_PING_FN(this),
10690
+ onIdlePing: CREATE_COMMIT_IDLE_PING_FN(this),
10595
10691
  onDispose: CREATE_COMMIT_DISPOSE_FN(this),
10596
10692
  onCommit: CREATE_COMMIT_FN(this),
10597
10693
  onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
@@ -11084,6 +11180,46 @@ class StrategyConnectionService {
11084
11180
  const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11085
11181
  return await strategy.getPositionCountdownMinutes(symbol, timestamp);
11086
11182
  };
11183
+ /**
11184
+ * Returns the number of minutes the position has been active since it opened.
11185
+ *
11186
+ * Delegates to ClientStrategy.getPositionActiveMinutes().
11187
+ * Returns null if no pending 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 active minutes (≥ 0) or null
11193
+ */
11194
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
11195
+ this.loggerService.log("strategyConnectionService getPositionActiveMinutes", {
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.getPositionActiveMinutes(symbol, timestamp);
11202
+ };
11203
+ /**
11204
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
11205
+ *
11206
+ * Delegates to ClientStrategy.getPositionWaitingMinutes().
11207
+ * Returns null if no scheduled signal exists.
11208
+ *
11209
+ * @param backtest - Whether running in backtest mode
11210
+ * @param symbol - Trading pair symbol
11211
+ * @param context - Execution context with strategyName, exchangeName, frameName
11212
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
11213
+ */
11214
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
11215
+ this.loggerService.log("strategyConnectionService getPositionWaitingMinutes", {
11216
+ symbol,
11217
+ context,
11218
+ });
11219
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11220
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
11221
+ return await strategy.getPositionWaitingMinutes(symbol, timestamp);
11222
+ };
11087
11223
  /**
11088
11224
  * Returns the best price reached in the profit direction during this position's life.
11089
11225
  *
@@ -13030,6 +13166,34 @@ const CALL_PING_SCHEDULED_FN = functoolsKit.trycatch(async (event, self) => {
13030
13166
  },
13031
13167
  defaultValue: null,
13032
13168
  });
13169
+ /**
13170
+ * Wrapper to call pingIdle method with error capture.
13171
+ */
13172
+ const CALL_PING_IDLE_FN = functoolsKit.trycatch(async (event, self) => {
13173
+ if (!self._target.pingIdle) {
13174
+ return;
13175
+ }
13176
+ if (await self.params.strategy.hasPendingSignal(event.backtest, event.symbol, {
13177
+ strategyName: event.strategyName,
13178
+ exchangeName: event.exchangeName,
13179
+ frameName: event.frameName,
13180
+ })) {
13181
+ return;
13182
+ }
13183
+ return await self._target.pingIdle(event);
13184
+ }, {
13185
+ fallback: (error) => {
13186
+ const message = "ActionProxy.pingIdle thrown";
13187
+ const payload = {
13188
+ error: functoolsKit.errorData(error),
13189
+ message: functoolsKit.getErrorMessage(error),
13190
+ };
13191
+ LOGGER_SERVICE$4.warn(message, payload);
13192
+ console.warn(message, payload);
13193
+ errorEmitter.next(error);
13194
+ },
13195
+ defaultValue: null,
13196
+ });
13033
13197
  /**
13034
13198
  * Wrapper to call pingActive method with error capture.
13035
13199
  */
@@ -13268,6 +13432,18 @@ class ActionProxy {
13268
13432
  async pingActive(event) {
13269
13433
  return await CALL_PING_ACTIVE_FN(event, this);
13270
13434
  }
13435
+ /**
13436
+ * Handles idle ping events with error capture.
13437
+ *
13438
+ * Wraps the user's pingIdle() method to catch and log any errors.
13439
+ * Called every tick while no signal is pending or scheduled.
13440
+ *
13441
+ * @param event - Idle ping data with symbol, strategy info, current price, timestamp
13442
+ * @returns Promise resolving to user's pingIdle() result or null on error
13443
+ */
13444
+ async pingIdle(event) {
13445
+ return await CALL_PING_IDLE_FN(event, this);
13446
+ }
13271
13447
  /**
13272
13448
  * Handles risk rejection events with error capture.
13273
13449
  *
@@ -13455,6 +13631,23 @@ const CALL_PING_SCHEDULED_CALLBACK_FN = functoolsKit.trycatch(async (self, event
13455
13631
  errorEmitter.next(error);
13456
13632
  },
13457
13633
  });
13634
+ /** Wrapper to call idle ping callback with error handling */
13635
+ const CALL_PING_IDLE_CALLBACK_FN = functoolsKit.trycatch(async (self, event, strategyName, frameName, backtest) => {
13636
+ if (self.params.callbacks?.onPingIdle) {
13637
+ await self.params.callbacks.onPingIdle(event, self.params.actionName, strategyName, frameName, backtest);
13638
+ }
13639
+ }, {
13640
+ fallback: (error, self) => {
13641
+ const message = "ClientAction CALL_PING_IDLE_CALLBACK_FN thrown";
13642
+ const payload = {
13643
+ error: functoolsKit.errorData(error),
13644
+ message: functoolsKit.getErrorMessage(error),
13645
+ };
13646
+ self.params.logger.warn(message, payload);
13647
+ console.warn(message, payload);
13648
+ errorEmitter.next(error);
13649
+ },
13650
+ });
13458
13651
  /** Wrapper to call active ping callback with error handling */
13459
13652
  const CALL_PING_ACTIVE_CALLBACK_FN = functoolsKit.trycatch(async (self, event, strategyName, frameName, backtest) => {
13460
13653
  if (self.params.callbacks?.onPingActive) {
@@ -13821,6 +14014,24 @@ class ClientAction {
13821
14014
  await CALL_PING_ACTIVE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
13822
14015
  }
13823
14016
  ;
14017
+ /**
14018
+ * Handles idle ping events when no signal is active.
14019
+ */
14020
+ async pingIdle(event) {
14021
+ this.params.logger.debug("ClientAction pingIdle", {
14022
+ actionName: this.params.actionName,
14023
+ strategyName: this.params.strategyName,
14024
+ frameName: this.params.frameName,
14025
+ });
14026
+ if (!this._handlerInstance) {
14027
+ await this.waitForInit();
14028
+ }
14029
+ // Call handler method if defined
14030
+ await this._handlerInstance?.pingIdle(event);
14031
+ // Call callback if defined
14032
+ await CALL_PING_IDLE_CALLBACK_FN(this, event, this.params.strategyName, this.params.frameName, event.backtest);
14033
+ }
14034
+ ;
13824
14035
  /**
13825
14036
  * Handles risk rejection events when signals fail risk validation.
13826
14037
  */
@@ -14073,6 +14284,21 @@ class ActionConnectionService {
14073
14284
  const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
14074
14285
  await action.pingActive(event);
14075
14286
  };
14287
+ /**
14288
+ * Routes idle ping event to appropriate ClientAction instance.
14289
+ *
14290
+ * @param event - Idle ping event data
14291
+ * @param backtest - Whether running in backtest mode
14292
+ * @param context - Execution context with action name, strategy name, exchange name, frame name
14293
+ */
14294
+ this.pingIdle = async (event, backtest, context) => {
14295
+ this.loggerService.log("actionConnectionService pingIdle", {
14296
+ backtest,
14297
+ context,
14298
+ });
14299
+ const action = this.getAction(context.actionName, context.strategyName, context.exchangeName, context.frameName, backtest);
14300
+ await action.pingIdle(event);
14301
+ };
14076
14302
  /**
14077
14303
  * Routes riskRejection event to appropriate ClientAction instance.
14078
14304
  *
@@ -15404,6 +15630,38 @@ class StrategyCoreService {
15404
15630
  await this.validate(context);
15405
15631
  return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
15406
15632
  };
15633
+ /**
15634
+ * Returns the number of minutes the position has been active since it opened.
15635
+ *
15636
+ * @param backtest - Whether running in backtest mode
15637
+ * @param symbol - Trading pair symbol
15638
+ * @param context - Execution context with strategyName, exchangeName, frameName
15639
+ * @returns Promise resolving to active minutes (≥ 0) or null
15640
+ */
15641
+ this.getPositionActiveMinutes = async (backtest, symbol, context) => {
15642
+ this.loggerService.log("strategyCoreService getPositionActiveMinutes", {
15643
+ symbol,
15644
+ context,
15645
+ });
15646
+ await this.validate(context);
15647
+ return await this.strategyConnectionService.getPositionActiveMinutes(backtest, symbol, context);
15648
+ };
15649
+ /**
15650
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
15651
+ *
15652
+ * @param backtest - Whether running in backtest mode
15653
+ * @param symbol - Trading pair symbol
15654
+ * @param context - Execution context with strategyName, exchangeName, frameName
15655
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
15656
+ */
15657
+ this.getPositionWaitingMinutes = async (backtest, symbol, context) => {
15658
+ this.loggerService.log("strategyCoreService getPositionWaitingMinutes", {
15659
+ symbol,
15660
+ context,
15661
+ });
15662
+ await this.validate(context);
15663
+ return await this.strategyConnectionService.getPositionWaitingMinutes(backtest, symbol, context);
15664
+ };
15407
15665
  /**
15408
15666
  * Returns the best price reached in the profit direction during this position's life.
15409
15667
  *
@@ -16144,6 +16402,27 @@ class ActionCoreService {
16144
16402
  await this.actionConnectionService.pingActive(event, backtest, { actionName, ...context });
16145
16403
  }
16146
16404
  };
16405
+ /**
16406
+ * Routes idle ping event to all registered actions for the strategy.
16407
+ *
16408
+ * Retrieves action list from strategy schema (IStrategySchema.actions)
16409
+ * and invokes the pingIdle handler on each ClientAction instance sequentially.
16410
+ * Called every tick when there is no pending or scheduled signal being monitored.
16411
+ *
16412
+ * @param backtest - Whether running in backtest mode (true) or live mode (false)
16413
+ * @param event - Idle state monitoring data
16414
+ * @param context - Strategy execution context with strategyName, exchangeName, frameName
16415
+ */
16416
+ this.pingIdle = async (backtest, event, context) => {
16417
+ this.loggerService.log("actionCoreService pingIdle", {
16418
+ context,
16419
+ });
16420
+ await this.validate(context);
16421
+ const { actions = [] } = this.strategySchemaService.get(context.strategyName);
16422
+ for (const actionName of actions) {
16423
+ await this.actionConnectionService.pingIdle(event, backtest, { actionName, ...context });
16424
+ }
16425
+ };
16147
16426
  /**
16148
16427
  * Routes risk rejection event to all registered actions for the strategy.
16149
16428
  *
@@ -32410,7 +32689,7 @@ class NotificationHelperService {
32410
32689
  const pendingSignal = await this.strategyCoreService.getPendingSignal(backtest, symbol, currentPrice, {
32411
32690
  strategyName: context.strategyName,
32412
32691
  exchangeName: context.exchangeName,
32413
- frameName: "",
32692
+ frameName: context.frameName,
32414
32693
  });
32415
32694
  if (!pendingSignal) {
32416
32695
  throw new Error(`SignalUtils notify No pending signal found symbol=${symbol} `);
@@ -35550,6 +35829,8 @@ const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
35550
35829
  const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
35551
35830
  const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
35552
35831
  const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
35832
+ const GET_POSITION_ACTIVE_MINUTES_METHOD_NAME = "strategy.getPositionActiveMinutes";
35833
+ const GET_POSITION_WAITING_MINUTES_METHOD_NAME = "strategy.getPositionWaitingMinutes";
35553
35834
  const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
35554
35835
  const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
35555
35836
  const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
@@ -36850,6 +37131,62 @@ async function getPositionCountdownMinutes(symbol) {
36850
37131
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36851
37132
  return await backtest.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
36852
37133
  }
37134
+ /**
37135
+ * Returns the number of minutes the position has been active since it opened.
37136
+ *
37137
+ * Returns null if no pending signal exists.
37138
+ *
37139
+ * @param symbol - Trading pair symbol
37140
+ * @returns Promise resolving to active minutes (≥ 0) or null
37141
+ *
37142
+ * @example
37143
+ * ```typescript
37144
+ * import { getPositionActiveMinutes } from "backtest-kit";
37145
+ *
37146
+ * const minutes = await getPositionActiveMinutes("BTCUSDT");
37147
+ * // e.g. 120 (position has been open for 2 hours)
37148
+ * ```
37149
+ */
37150
+ async function getPositionActiveMinutes(symbol) {
37151
+ backtest.loggerService.info(GET_POSITION_ACTIVE_MINUTES_METHOD_NAME, { symbol });
37152
+ if (!ExecutionContextService.hasContext()) {
37153
+ throw new Error("getPositionActiveMinutes requires an execution context");
37154
+ }
37155
+ if (!MethodContextService.hasContext()) {
37156
+ throw new Error("getPositionActiveMinutes requires a method context");
37157
+ }
37158
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37159
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37160
+ return await backtest.strategyCoreService.getPositionActiveMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
37161
+ }
37162
+ /**
37163
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
37164
+ *
37165
+ * Returns null if no scheduled signal exists.
37166
+ *
37167
+ * @param symbol - Trading pair symbol
37168
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
37169
+ *
37170
+ * @example
37171
+ * ```typescript
37172
+ * import { getPositionWaitingMinutes } from "backtest-kit";
37173
+ *
37174
+ * const minutes = await getPositionWaitingMinutes("BTCUSDT");
37175
+ * // e.g. 15 (scheduled signal has been waiting 15 minutes for activation)
37176
+ * ```
37177
+ */
37178
+ async function getPositionWaitingMinutes(symbol) {
37179
+ backtest.loggerService.info(GET_POSITION_WAITING_MINUTES_METHOD_NAME, { symbol });
37180
+ if (!ExecutionContextService.hasContext()) {
37181
+ throw new Error("getPositionWaitingMinutes requires an execution context");
37182
+ }
37183
+ if (!MethodContextService.hasContext()) {
37184
+ throw new Error("getPositionWaitingMinutes requires a method context");
37185
+ }
37186
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37187
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37188
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
37189
+ }
36853
37190
  /**
36854
37191
  * Returns the best price reached in the profit direction during this position's life.
36855
37192
  *
@@ -37656,6 +37993,8 @@ const LISTEN_SCHEDULE_PING_METHOD_NAME = "event.listenSchedulePing";
37656
37993
  const LISTEN_SCHEDULE_PING_ONCE_METHOD_NAME = "event.listenSchedulePingOnce";
37657
37994
  const LISTEN_ACTIVE_PING_METHOD_NAME = "event.listenActivePing";
37658
37995
  const LISTEN_ACTIVE_PING_ONCE_METHOD_NAME = "event.listenActivePingOnce";
37996
+ const LISTEN_IDLE_PING_METHOD_NAME = "event.listenIdlePing";
37997
+ const LISTEN_IDLE_PING_ONCE_METHOD_NAME = "event.listenIdlePingOnce";
37659
37998
  const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
37660
37999
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
37661
38000
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
@@ -38831,6 +39170,45 @@ function listenActivePingOnce(filterFn, fn) {
38831
39170
  };
38832
39171
  return disposeFn = listenActivePing(wrappedFn);
38833
39172
  }
39173
+ /**
39174
+ * Subscribes to idle ping events with queued async processing.
39175
+ *
39176
+ * Emits every tick when there is no pending or scheduled signal being monitored.
39177
+ *
39178
+ * @param fn - Callback function to handle idle ping events
39179
+ * @returns Unsubscribe function to stop listening
39180
+ */
39181
+ function listenIdlePing(fn) {
39182
+ backtest.loggerService.log(LISTEN_IDLE_PING_METHOD_NAME);
39183
+ const wrappedFn = async (event) => {
39184
+ if (await functoolsKit.not(backtest.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
39185
+ strategyName: event.strategyName,
39186
+ exchangeName: event.exchangeName,
39187
+ frameName: event.frameName,
39188
+ }))) {
39189
+ await fn(event);
39190
+ }
39191
+ };
39192
+ return idlePingSubject.subscribe(functoolsKit.queued(wrappedFn));
39193
+ }
39194
+ /**
39195
+ * Subscribes to filtered idle ping events with one-time execution.
39196
+ *
39197
+ * @param filterFn - Predicate to filter events
39198
+ * @param fn - Callback function to handle the matching event
39199
+ * @returns Unsubscribe function to cancel the listener before it fires
39200
+ */
39201
+ function listenIdlePingOnce(filterFn, fn) {
39202
+ backtest.loggerService.log(LISTEN_IDLE_PING_ONCE_METHOD_NAME);
39203
+ let disposeFn;
39204
+ const wrappedFn = async (event) => {
39205
+ if (filterFn(event)) {
39206
+ await fn(event);
39207
+ disposeFn && disposeFn();
39208
+ }
39209
+ };
39210
+ return disposeFn = listenIdlePing(wrappedFn);
39211
+ }
38834
39212
  /**
38835
39213
  * Subscribes to strategy management events with queued async processing.
38836
39214
  *
@@ -39120,6 +39498,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPar
39120
39498
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
39121
39499
  const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
39122
39500
  const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
39501
+ const BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "BacktestUtils.getPositionActiveMinutes";
39502
+ const BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "BacktestUtils.getPositionWaitingMinutes";
39123
39503
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
39124
39504
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
39125
39505
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
@@ -40075,6 +40455,60 @@ class BacktestUtils {
40075
40455
  }
40076
40456
  return await backtest.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
40077
40457
  };
40458
+ /**
40459
+ * Returns the number of minutes the position has been active since it opened.
40460
+ *
40461
+ * Returns null if no pending signal exists.
40462
+ *
40463
+ * @param symbol - Trading pair symbol
40464
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40465
+ * @returns Active minutes (≥ 0), or null if no active position
40466
+ */
40467
+ this.getPositionActiveMinutes = async (symbol, context) => {
40468
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
40469
+ symbol,
40470
+ context,
40471
+ });
40472
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40473
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40474
+ {
40475
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40476
+ riskName &&
40477
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
40478
+ riskList &&
40479
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40480
+ actions &&
40481
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
40482
+ }
40483
+ return await backtest.strategyCoreService.getPositionActiveMinutes(true, symbol, context);
40484
+ };
40485
+ /**
40486
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
40487
+ *
40488
+ * Returns null if no scheduled signal exists.
40489
+ *
40490
+ * @param symbol - Trading pair symbol
40491
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40492
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
40493
+ */
40494
+ this.getPositionWaitingMinutes = async (symbol, context) => {
40495
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
40496
+ symbol,
40497
+ context,
40498
+ });
40499
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40500
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40501
+ {
40502
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40503
+ riskName &&
40504
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
40505
+ riskList &&
40506
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40507
+ actions &&
40508
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
40509
+ }
40510
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(true, symbol, context);
40511
+ };
40078
40512
  /**
40079
40513
  * Returns the best price reached in the profit direction during this position's life.
40080
40514
  *
@@ -41716,6 +42150,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
41716
42150
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
41717
42151
  const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
41718
42152
  const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
42153
+ const LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "LiveUtils.getPositionActiveMinutes";
42154
+ const LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "LiveUtils.getPositionWaitingMinutes";
41719
42155
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
41720
42156
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
41721
42157
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
@@ -42750,6 +43186,68 @@ class LiveUtils {
42750
43186
  frameName: "",
42751
43187
  });
42752
43188
  };
43189
+ /**
43190
+ * Returns the number of minutes the position has been active since it opened.
43191
+ *
43192
+ * Returns null if no pending signal exists.
43193
+ *
43194
+ * @param symbol - Trading pair symbol
43195
+ * @param context - Execution context with strategyName and exchangeName
43196
+ * @returns Active minutes (≥ 0), or null if no active position
43197
+ */
43198
+ this.getPositionActiveMinutes = async (symbol, context) => {
43199
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, {
43200
+ symbol,
43201
+ context,
43202
+ });
43203
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
43204
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
43205
+ {
43206
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43207
+ riskName &&
43208
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
43209
+ riskList &&
43210
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
43211
+ actions &&
43212
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
43213
+ }
43214
+ return await backtest.strategyCoreService.getPositionActiveMinutes(false, symbol, {
43215
+ strategyName: context.strategyName,
43216
+ exchangeName: context.exchangeName,
43217
+ frameName: "",
43218
+ });
43219
+ };
43220
+ /**
43221
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
43222
+ *
43223
+ * Returns null if no scheduled signal exists.
43224
+ *
43225
+ * @param symbol - Trading pair symbol
43226
+ * @param context - Execution context with strategyName and exchangeName
43227
+ * @returns Waiting minutes (≥ 0), or null if no scheduled signal
43228
+ */
43229
+ this.getPositionWaitingMinutes = async (symbol, context) => {
43230
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES, {
43231
+ symbol,
43232
+ context,
43233
+ });
43234
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43235
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43236
+ {
43237
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43238
+ riskName &&
43239
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
43240
+ riskList &&
43241
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43242
+ actions &&
43243
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
43244
+ }
43245
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(false, symbol, {
43246
+ strategyName: context.strategyName,
43247
+ exchangeName: context.exchangeName,
43248
+ frameName: "",
43249
+ });
43250
+ };
42753
43251
  /**
42754
43252
  * Returns the best price reached in the profit direction during this position's life.
42755
43253
  *
@@ -45668,6 +46166,7 @@ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45668
46166
  const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45669
46167
  const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45670
46168
  const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
46169
+ const RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL = "RecentAdapter.getMinutesSinceLatestSignalCreated";
45671
46170
  /**
45672
46171
  * Builds a composite storage key from context parts.
45673
46172
  * Includes backtest flag as the last segment to prevent live/backtest collisions.
@@ -45726,6 +46225,23 @@ class RecentPersistBacktestUtils {
45726
46225
  });
45727
46226
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45728
46227
  };
46228
+ /**
46229
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46230
+ * @param timestamp - Current timestamp in milliseconds
46231
+ * @param symbol - Trading pair symbol
46232
+ * @param strategyName - Strategy identifier
46233
+ * @param exchangeName - Exchange identifier
46234
+ * @param frameName - Frame identifier
46235
+ * @param backtest - Flag indicating if the context is backtest or live
46236
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46237
+ */
46238
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46239
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46240
+ if (!signal) {
46241
+ return null;
46242
+ }
46243
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46244
+ };
45729
46245
  }
45730
46246
  }
45731
46247
  /**
@@ -45768,6 +46284,23 @@ class RecentMemoryBacktestUtils {
45768
46284
  backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45769
46285
  return this._signals.get(key) ?? null;
45770
46286
  };
46287
+ /**
46288
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46289
+ * @param timestamp - Current timestamp in milliseconds
46290
+ * @param symbol - Trading pair symbol
46291
+ * @param strategyName - Strategy identifier
46292
+ * @param exchangeName - Exchange identifier
46293
+ * @param frameName - Frame identifier
46294
+ * @param backtest - Flag indicating if the context is backtest or live
46295
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46296
+ */
46297
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46298
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46299
+ if (!signal) {
46300
+ return null;
46301
+ }
46302
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46303
+ };
45771
46304
  }
45772
46305
  }
45773
46306
  /**
@@ -45811,6 +46344,23 @@ class RecentPersistLiveUtils {
45811
46344
  });
45812
46345
  return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45813
46346
  };
46347
+ /**
46348
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46349
+ * @param timestamp - Current timestamp in milliseconds
46350
+ * @param symbol - Trading pair symbol
46351
+ * @param strategyName - Strategy identifier
46352
+ * @param exchangeName - Exchange identifier
46353
+ * @param frameName - Frame identifier
46354
+ * @param backtest - Flag indicating if the context is backtest or live
46355
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46356
+ */
46357
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46358
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46359
+ if (!signal) {
46360
+ return null;
46361
+ }
46362
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46363
+ };
45814
46364
  }
45815
46365
  }
45816
46366
  /**
@@ -45853,6 +46403,23 @@ class RecentMemoryLiveUtils {
45853
46403
  backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45854
46404
  return this._signals.get(key) ?? null;
45855
46405
  };
46406
+ /**
46407
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46408
+ * @param timestamp - Current timestamp in milliseconds
46409
+ * @param symbol - Trading pair symbol
46410
+ * @param strategyName - Strategy identifier
46411
+ * @param exchangeName - Exchange identifier
46412
+ * @param frameName - Frame identifier
46413
+ * @param backtest - Flag indicating if the context is backtest or live
46414
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46415
+ */
46416
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest) => {
46417
+ const signal = await this.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest);
46418
+ if (!signal) {
46419
+ return null;
46420
+ }
46421
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46422
+ };
45856
46423
  }
45857
46424
  }
45858
46425
  /**
@@ -45899,6 +46466,32 @@ class RecentBacktestAdapter {
45899
46466
  });
45900
46467
  return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45901
46468
  };
46469
+ /**
46470
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46471
+ * Proxies call to the underlying storage adapter.
46472
+ * @param timestamp - Current timestamp in milliseconds
46473
+ * @param symbol - Trading pair symbol
46474
+ * @param strategyName - Strategy identifier
46475
+ * @param exchangeName - Exchange identifier
46476
+ * @param frameName - Frame identifier
46477
+ * @param backtest - Flag indicating if the context is backtest or live
46478
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46479
+ */
46480
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
46481
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46482
+ symbol,
46483
+ strategyName,
46484
+ exchangeName,
46485
+ frameName,
46486
+ backtest: backtest$1,
46487
+ timestamp,
46488
+ });
46489
+ const signal = await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46490
+ if (!signal) {
46491
+ return null;
46492
+ }
46493
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46494
+ };
45902
46495
  /**
45903
46496
  * Sets the storage adapter constructor.
45904
46497
  * All future storage operations will use this adapter.
@@ -45977,6 +46570,32 @@ class RecentLiveAdapter {
45977
46570
  });
45978
46571
  return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45979
46572
  };
46573
+ /**
46574
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46575
+ * Proxies call to the underlying storage adapter.
46576
+ * @param timestamp - Current timestamp in milliseconds
46577
+ * @param symbol - Trading pair symbol
46578
+ * @param strategyName - Strategy identifier
46579
+ * @param exchangeName - Exchange identifier
46580
+ * @param frameName - Frame identifier
46581
+ * @param backtest - Flag indicating if the context is backtest or live
46582
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46583
+ */
46584
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, strategyName, exchangeName, frameName, backtest$1) => {
46585
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
46586
+ symbol,
46587
+ strategyName,
46588
+ exchangeName,
46589
+ frameName,
46590
+ backtest: backtest$1,
46591
+ timestamp,
46592
+ });
46593
+ const signal = await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
46594
+ if (!signal) {
46595
+ return null;
46596
+ }
46597
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46598
+ };
45980
46599
  /**
45981
46600
  * Sets the storage adapter constructor.
45982
46601
  * All future storage operations will use this adapter.
@@ -46085,6 +46704,27 @@ class RecentAdapter {
46085
46704
  }
46086
46705
  return null;
46087
46706
  };
46707
+ /**
46708
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46709
+ * Searches backtest storage first, then live storage.
46710
+ * @param timestamp - Current timestamp in milliseconds
46711
+ * @param symbol - Trading pair symbol
46712
+ * @param context - Execution context with strategyName, exchangeName, and frameName
46713
+ * @returns Whole minutes since the latest signal was created, or null if no signal found
46714
+ * @throws Error if RecentAdapter is not enabled
46715
+ */
46716
+ this.getMinutesSinceLatestSignalCreated = async (timestamp, symbol, context) => {
46717
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_MINUTES_SINCE_LATEST_SIGNAL, {
46718
+ symbol,
46719
+ context,
46720
+ timestamp,
46721
+ });
46722
+ const signal = await this.getLatestSignal(symbol, context);
46723
+ if (!signal) {
46724
+ return null;
46725
+ }
46726
+ return Math.floor((timestamp - signal.timestamp) / (1000 * 60));
46727
+ };
46088
46728
  }
46089
46729
  }
46090
46730
  /**
@@ -46104,6 +46744,7 @@ const RecentLive = new RecentLiveAdapter();
46104
46744
  const RecentBacktest = new RecentBacktestAdapter();
46105
46745
 
46106
46746
  const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
46747
+ const GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME = "signal.getMinutesSinceLatestSignalCreated";
46107
46748
  /**
46108
46749
  * Returns the latest signal (pending or closed) for the current strategy context.
46109
46750
  *
@@ -46141,6 +46782,42 @@ async function getLatestSignal(symbol) {
46141
46782
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46142
46783
  return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
46143
46784
  }
46785
+ /**
46786
+ * Returns the number of whole minutes elapsed since the latest signal's creation timestamp.
46787
+ *
46788
+ * Does not distinguish between active and closed signals — measures time since
46789
+ * whichever signal was recorded last. Useful for cooldown logic after a stop-loss.
46790
+ *
46791
+ * Searches backtest storage first, then live storage.
46792
+ * Returns null if no signal exists at all.
46793
+ *
46794
+ * Automatically detects backtest/live mode from execution context.
46795
+ *
46796
+ * @param symbol - Trading pair symbol
46797
+ * @param timestamp - Current timestamp in milliseconds
46798
+ * @returns Promise resolving to whole minutes since the latest signal was created, or null
46799
+ *
46800
+ * @example
46801
+ * ```typescript
46802
+ * import { getMinutesSinceLatestSignalCreated } from "backtest-kit";
46803
+ *
46804
+ * const minutes = await getMinutesSinceLatestSignalCreated("BTCUSDT", Date.now());
46805
+ * if (minutes !== null && minutes < 24 * 60) {
46806
+ * return; // cooldown — skip new signal for 24 hours after last signal
46807
+ * }
46808
+ * ```
46809
+ */
46810
+ async function getMinutesSinceLatestSignalCreated(symbol, timestamp) {
46811
+ backtest.loggerService.info(GET_MINUTES_SINCE_LATEST_SIGNAL_CREATED_METHOD_NAME, { symbol, timestamp });
46812
+ if (!ExecutionContextService.hasContext()) {
46813
+ throw new Error("getMinutesSinceLatestSignalCreated requires an execution context");
46814
+ }
46815
+ if (!MethodContextService.hasContext()) {
46816
+ throw new Error("getMinutesSinceLatestSignalCreated requires a method context");
46817
+ }
46818
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
46819
+ return await Recent.getMinutesSinceLatestSignalCreated(timestamp, symbol, { exchangeName, frameName, strategyName });
46820
+ }
46144
46821
 
46145
46822
  const DEFAULT_BM25_K1 = 1.5;
46146
46823
  const DEFAULT_BM25_B = 0.75;
@@ -48203,7 +48880,7 @@ const LOGGER_SERVICE$2 = new LoggerService();
48203
48880
  * Default configuration that enables all report services.
48204
48881
  * Used when no specific configuration is provided to enable().
48205
48882
  */
48206
- const WILDCARD_TARGET$1 = {
48883
+ const WILDCARD_TARGET$2 = {
48207
48884
  backtest: true,
48208
48885
  strategy: true,
48209
48886
  breakeven: true,
@@ -48254,7 +48931,7 @@ class ReportUtils {
48254
48931
  *
48255
48932
  * @returns Cleanup function that unsubscribes from all enabled services
48256
48933
  */
48257
- 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) => {
48934
+ 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) => {
48258
48935
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
48259
48936
  backtest: bt,
48260
48937
  breakeven,
@@ -48346,7 +49023,7 @@ class ReportUtils {
48346
49023
  * Report.disable();
48347
49024
  * ```
48348
49025
  */
48349
- 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) => {
49026
+ 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) => {
48350
49027
  LOGGER_SERVICE$2.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
48351
49028
  backtest: bt,
48352
49029
  breakeven,
@@ -48470,7 +49147,7 @@ const LOGGER_SERVICE$1 = new LoggerService();
48470
49147
  * Default configuration that enables all markdown services.
48471
49148
  * Used when no specific configuration is provided to `enable()`.
48472
49149
  */
48473
- const WILDCARD_TARGET = {
49150
+ const WILDCARD_TARGET$1 = {
48474
49151
  backtest: true,
48475
49152
  breakeven: true,
48476
49153
  heat: true,
@@ -48521,7 +49198,7 @@ class MarkdownUtils {
48521
49198
  *
48522
49199
  * @returns Cleanup function that unsubscribes from all enabled services
48523
49200
  */
48524
- 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) => {
49201
+ 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) => {
48525
49202
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_ENABLE, {
48526
49203
  backtest: bt,
48527
49204
  breakeven,
@@ -48615,7 +49292,7 @@ class MarkdownUtils {
48615
49292
  * Markdown.disable();
48616
49293
  * ```
48617
49294
  */
48618
- 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) => {
49295
+ 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) => {
48619
49296
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_DISABLE, {
48620
49297
  backtest: bt,
48621
49298
  breakeven,
@@ -48696,7 +49373,7 @@ class MarkdownUtils {
48696
49373
  * @param config.highest_profit - Clear highest profit report data
48697
49374
  * @param config.max_drawdown - Clear max drawdown report data
48698
49375
  */
48699
- 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) => {
49376
+ 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) => {
48700
49377
  LOGGER_SERVICE$1.debug(MARKDOWN_METHOD_NAME_CLEAR, {
48701
49378
  backtest: bt,
48702
49379
  breakeven,
@@ -49468,6 +50145,7 @@ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49468
50145
  /** List of all global subjects whose listeners should be snapshotted for session isolation */
49469
50146
  const SUBJECT_ISOLATION_LIST = [
49470
50147
  activePingSubject,
50148
+ idlePingSubject,
49471
50149
  backtestScheduleOpenSubject,
49472
50150
  breakevenSubject,
49473
50151
  doneBacktestSubject,
@@ -51108,6 +51786,8 @@ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.
51108
51786
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
51109
51787
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
51110
51788
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
51789
+ const REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES = "ReflectUtils.getPositionActiveMinutes";
51790
+ const REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES = "ReflectUtils.getPositionWaitingMinutes";
51111
51791
  const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
51112
51792
  const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
51113
51793
  const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
@@ -51382,6 +52062,52 @@ class ReflectUtils {
51382
52062
  }
51383
52063
  return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
51384
52064
  };
52065
+ /**
52066
+ * Returns the number of minutes the position has been active since it opened.
52067
+ *
52068
+ * Returns null if no pending signal exists.
52069
+ *
52070
+ * @param symbol - Trading pair symbol
52071
+ * @param context - Execution context with strategyName, exchangeName and frameName
52072
+ * @param backtest - True if backtest mode, false if live mode (default: false)
52073
+ * @returns Promise resolving to active minutes (≥ 0) or null
52074
+ */
52075
+ this.getPositionActiveMinutes = async (symbol, context, backtest$1 = false) => {
52076
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES, { symbol, context });
52077
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52078
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52079
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52080
+ {
52081
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
52082
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES);
52083
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
52084
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_ACTIVE_MINUTES));
52085
+ }
52086
+ return await backtest.strategyCoreService.getPositionActiveMinutes(backtest$1, symbol, context);
52087
+ };
52088
+ /**
52089
+ * Returns the number of minutes the scheduled signal has been waiting for activation.
52090
+ *
52091
+ * Returns null if no scheduled signal exists.
52092
+ *
52093
+ * @param symbol - Trading pair symbol
52094
+ * @param context - Execution context with strategyName, exchangeName and frameName
52095
+ * @param backtest - True if backtest mode, false if live mode (default: false)
52096
+ * @returns Promise resolving to waiting minutes (≥ 0) or null
52097
+ */
52098
+ this.getPositionWaitingMinutes = async (symbol, context, backtest$1 = false) => {
52099
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES, { symbol, context });
52100
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52101
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52102
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52103
+ {
52104
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
52105
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES);
52106
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
52107
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_WAITING_MINUTES));
52108
+ }
52109
+ return await backtest.strategyCoreService.getPositionWaitingMinutes(backtest$1, symbol, context);
52110
+ };
51385
52111
  /**
51386
52112
  * Returns the number of minutes elapsed since the highest profit price was recorded.
51387
52113
  *
@@ -53658,6 +54384,23 @@ const StorageLive = new StorageLiveAdapter();
53658
54384
  */
53659
54385
  const StorageBacktest = new StorageBacktestAdapter();
53660
54386
 
54387
+ /**
54388
+ * Default configuration that enables all notification types.
54389
+ * Used when no specific configuration is provided to enable().
54390
+ */
54391
+ const WILDCARD_TARGET = {
54392
+ signal: true,
54393
+ partial_profit: true,
54394
+ partial_loss: true,
54395
+ breakeven: true,
54396
+ strategy_commit: true,
54397
+ signal_sync: true,
54398
+ risk: true,
54399
+ info: true,
54400
+ common_error: true,
54401
+ critical_error: true,
54402
+ validation_error: true,
54403
+ };
53661
54404
  /**
53662
54405
  * Generates a unique key for notification identification.
53663
54406
  * @returns Random string identifier
@@ -55713,64 +56456,152 @@ class NotificationAdapter {
55713
56456
  *
55714
56457
  * @returns Cleanup function that unsubscribes from all emitters
55715
56458
  */
55716
- this.enable = functoolsKit.singleshot(() => {
56459
+ this.enable = functoolsKit.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) => {
55717
56460
  backtest.loggerService.info(NOTIFICATION_ADAPTER_METHOD_NAME_ENABLE);
55718
56461
  let unLive;
55719
56462
  let unBacktest;
55720
56463
  {
55721
- const unBacktestSignal = signalBacktestEmitter.subscribe((data) => NotificationBacktest.handleSignal(data));
56464
+ const unBacktestSignal = signalBacktestEmitter.subscribe(async (data) => {
56465
+ if (signal) {
56466
+ await NotificationBacktest.handleSignal(data);
56467
+ }
56468
+ });
55722
56469
  const unBacktestPartialProfit = partialProfitSubject
55723
56470
  .filter(({ backtest }) => backtest)
55724
- .connect((data) => NotificationBacktest.handlePartialProfit(data));
56471
+ .connect(async (data) => {
56472
+ if (partial_profit) {
56473
+ await NotificationBacktest.handlePartialProfit(data);
56474
+ }
56475
+ });
55725
56476
  const unBacktestPartialLoss = partialLossSubject
55726
56477
  .filter(({ backtest }) => backtest)
55727
- .connect((data) => NotificationBacktest.handlePartialLoss(data));
56478
+ .connect(async (data) => {
56479
+ if (partial_loss) {
56480
+ await NotificationBacktest.handlePartialLoss(data);
56481
+ }
56482
+ });
55728
56483
  const unBacktestBreakeven = breakevenSubject
55729
56484
  .filter(({ backtest }) => backtest)
55730
- .connect((data) => NotificationBacktest.handleBreakeven(data));
56485
+ .connect(async (data) => {
56486
+ if (breakeven) {
56487
+ await NotificationBacktest.handleBreakeven(data);
56488
+ }
56489
+ });
55731
56490
  const unBacktestStrategyCommit = strategyCommitSubject
55732
56491
  .filter(({ backtest }) => backtest)
55733
- .connect((data) => NotificationBacktest.handleStrategyCommit(data));
56492
+ .connect(async (data) => {
56493
+ if (strategy_commit) {
56494
+ await NotificationBacktest.handleStrategyCommit(data);
56495
+ }
56496
+ });
55734
56497
  const unBacktestSync = syncSubject
55735
56498
  .filter(({ backtest }) => backtest)
55736
- .connect((data) => NotificationBacktest.handleSync(data));
56499
+ .connect(async (data) => {
56500
+ if (signal_sync) {
56501
+ await NotificationBacktest.handleSync(data);
56502
+ }
56503
+ });
55737
56504
  const unBacktestRisk = riskSubject
55738
56505
  .filter(({ backtest }) => backtest)
55739
- .connect((data) => NotificationBacktest.handleRisk(data));
55740
- const unBacktestError = errorEmitter.subscribe((error) => NotificationBacktest.handleError(error));
55741
- const unBacktestExit = exitEmitter.subscribe((error) => NotificationBacktest.handleCriticalError(error));
55742
- const unBacktestValidation = validationSubject.subscribe((error) => NotificationBacktest.handleValidationError(error));
56506
+ .connect(async (data) => {
56507
+ if (risk) {
56508
+ await NotificationBacktest.handleRisk(data);
56509
+ }
56510
+ });
56511
+ const unBacktestError = errorEmitter.subscribe(async (error) => {
56512
+ if (common_error) {
56513
+ await NotificationBacktest.handleError(error);
56514
+ }
56515
+ });
56516
+ const unBacktestExit = exitEmitter.subscribe(async (error) => {
56517
+ if (critical_error) {
56518
+ await NotificationBacktest.handleCriticalError(error);
56519
+ }
56520
+ });
56521
+ const unBacktestValidation = validationSubject.subscribe(async (error) => {
56522
+ if (validation_error) {
56523
+ await NotificationBacktest.handleValidationError(error);
56524
+ }
56525
+ });
55743
56526
  const unBacktestSignalNotify = signalNotifySubject
55744
56527
  .filter(({ backtest }) => backtest)
55745
- .connect((data) => NotificationBacktest.handleSignalNotify(data));
56528
+ .connect(async (data) => {
56529
+ if (info) {
56530
+ await NotificationBacktest.handleSignalNotify(data);
56531
+ }
56532
+ });
55746
56533
  unBacktest = functoolsKit.compose(() => unBacktestSignal(), () => unBacktestPartialProfit(), () => unBacktestPartialLoss(), () => unBacktestBreakeven(), () => unBacktestStrategyCommit(), () => unBacktestSync(), () => unBacktestRisk(), () => unBacktestError(), () => unBacktestExit(), () => unBacktestValidation(), () => unBacktestSignalNotify());
55747
56534
  }
55748
56535
  {
55749
- const unLiveSignal = signalLiveEmitter.subscribe((data) => NotificationLive.handleSignal(data));
56536
+ const unLiveSignal = signalLiveEmitter.subscribe(async (data) => {
56537
+ if (signal) {
56538
+ await NotificationLive.handleSignal(data);
56539
+ }
56540
+ });
55750
56541
  const unLivePartialProfit = partialProfitSubject
55751
56542
  .filter(({ backtest }) => !backtest)
55752
- .connect((data) => NotificationLive.handlePartialProfit(data));
56543
+ .connect(async (data) => {
56544
+ if (partial_profit) {
56545
+ await NotificationLive.handlePartialProfit(data);
56546
+ }
56547
+ });
55753
56548
  const unLivePartialLoss = partialLossSubject
55754
56549
  .filter(({ backtest }) => !backtest)
55755
- .connect((data) => NotificationLive.handlePartialLoss(data));
56550
+ .connect(async (data) => {
56551
+ if (partial_loss) {
56552
+ await NotificationLive.handlePartialLoss(data);
56553
+ }
56554
+ });
55756
56555
  const unLiveBreakeven = breakevenSubject
55757
56556
  .filter(({ backtest }) => !backtest)
55758
- .connect((data) => NotificationLive.handleBreakeven(data));
56557
+ .connect(async (data) => {
56558
+ if (breakeven) {
56559
+ await NotificationLive.handleBreakeven(data);
56560
+ }
56561
+ });
55759
56562
  const unLiveStrategyCommit = strategyCommitSubject
55760
56563
  .filter(({ backtest }) => !backtest)
55761
- .connect((data) => NotificationLive.handleStrategyCommit(data));
56564
+ .connect(async (data) => {
56565
+ if (strategy_commit) {
56566
+ await NotificationLive.handleStrategyCommit(data);
56567
+ }
56568
+ });
55762
56569
  const unLiveSync = syncSubject
55763
56570
  .filter(({ backtest }) => !backtest)
55764
- .connect((data) => NotificationLive.handleSync(data));
56571
+ .connect(async (data) => {
56572
+ if (signal_sync) {
56573
+ await NotificationLive.handleSync(data);
56574
+ }
56575
+ });
55765
56576
  const unLiveRisk = riskSubject
55766
56577
  .filter(({ backtest }) => !backtest)
55767
- .connect((data) => NotificationLive.handleRisk(data));
55768
- const unLiveError = errorEmitter.subscribe((error) => NotificationLive.handleError(error));
55769
- const unLiveExit = exitEmitter.subscribe((error) => NotificationLive.handleCriticalError(error));
55770
- const unLiveValidation = validationSubject.subscribe((error) => NotificationLive.handleValidationError(error));
56578
+ .connect(async (data) => {
56579
+ if (risk) {
56580
+ await NotificationLive.handleRisk(data);
56581
+ }
56582
+ });
56583
+ const unLiveError = errorEmitter.subscribe(async (error) => {
56584
+ if (common_error) {
56585
+ await NotificationLive.handleError(error);
56586
+ }
56587
+ });
56588
+ const unLiveExit = exitEmitter.subscribe(async (error) => {
56589
+ if (critical_error) {
56590
+ await NotificationLive.handleCriticalError(error);
56591
+ }
56592
+ });
56593
+ const unLiveValidation = validationSubject.subscribe(async (error) => {
56594
+ if (validation_error) {
56595
+ await NotificationLive.handleValidationError(error);
56596
+ }
56597
+ });
55771
56598
  const unLiveSignalNotify = signalNotifySubject
55772
56599
  .filter(({ backtest }) => !backtest)
55773
- .connect((data) => NotificationLive.handleSignalNotify(data));
56600
+ .connect(async (data) => {
56601
+ if (info) {
56602
+ await NotificationLive.handleSignalNotify(data);
56603
+ }
56604
+ });
55774
56605
  unLive = functoolsKit.compose(() => unLiveSignal(), () => unLivePartialProfit(), () => unLivePartialLoss(), () => unLiveBreakeven(), () => unLiveStrategyCommit(), () => unLiveSync(), () => unLiveRisk(), () => unLiveError(), () => unLiveExit(), () => unLiveValidation(), () => unLiveSignalNotify());
55775
56606
  }
55776
56607
  return () => {
@@ -57435,6 +58266,7 @@ const METHOD_NAME_PARTIAL_PROFIT_AVAILABLE = "ActionBase.partialProfitAvailable"
57435
58266
  const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
57436
58267
  const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
57437
58268
  const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
58269
+ const METHOD_NAME_PING_IDLE = "ActionBase.pingIdle";
57438
58270
  const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
57439
58271
  const METHOD_NAME_DISPOSE = "ActionBase.dispose";
57440
58272
  const DEFAULT_SOURCE = "default";
@@ -57828,6 +58660,26 @@ class ActionBase {
57828
58660
  source,
57829
58661
  });
57830
58662
  }
58663
+ /**
58664
+ * Handles idle ping events when no signal is active.
58665
+ *
58666
+ * Called every tick while no signal is pending or scheduled.
58667
+ * Use to monitor idle strategy state and implement entry condition logic.
58668
+ *
58669
+ * Triggered by: ActionCoreService.pingIdle() via StrategyConnectionService
58670
+ * Source: idlePingSubject.next() in CREATE_COMMIT_IDLE_PING_FN callback
58671
+ * Frequency: Every tick while no signal is pending or scheduled
58672
+ *
58673
+ * Default implementation: Logs idle ping event.
58674
+ *
58675
+ * @param event - Idle ping data with symbol, strategy info, current price, timestamp
58676
+ */
58677
+ pingIdle(event, source = DEFAULT_SOURCE) {
58678
+ LOGGER_SERVICE.info(METHOD_NAME_PING_IDLE, {
58679
+ event,
58680
+ source,
58681
+ });
58682
+ }
57831
58683
  /**
57832
58684
  * Handles risk rejection events when signals fail risk validation.
57833
58685
  *
@@ -58271,10 +59123,12 @@ exports.getFrameSchema = getFrameSchema;
58271
59123
  exports.getLatestSignal = getLatestSignal;
58272
59124
  exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
58273
59125
  exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
59126
+ exports.getMinutesSinceLatestSignalCreated = getMinutesSinceLatestSignalCreated;
58274
59127
  exports.getMode = getMode;
58275
59128
  exports.getNextCandles = getNextCandles;
58276
59129
  exports.getOrderBook = getOrderBook;
58277
59130
  exports.getPendingSignal = getPendingSignal;
59131
+ exports.getPositionActiveMinutes = getPositionActiveMinutes;
58278
59132
  exports.getPositionCountdownMinutes = getPositionCountdownMinutes;
58279
59133
  exports.getPositionDrawdownMinutes = getPositionDrawdownMinutes;
58280
59134
  exports.getPositionEffectivePrice = getPositionEffectivePrice;
@@ -58303,6 +59157,7 @@ exports.getPositionPartialOverlap = getPositionPartialOverlap;
58303
59157
  exports.getPositionPartials = getPositionPartials;
58304
59158
  exports.getPositionPnlCost = getPositionPnlCost;
58305
59159
  exports.getPositionPnlPercent = getPositionPnlPercent;
59160
+ exports.getPositionWaitingMinutes = getPositionWaitingMinutes;
58306
59161
  exports.getRawCandles = getRawCandles;
58307
59162
  exports.getRiskSchema = getRiskSchema;
58308
59163
  exports.getScheduledSignal = getScheduledSignal;
@@ -58341,6 +59196,8 @@ exports.listenError = listenError;
58341
59196
  exports.listenExit = listenExit;
58342
59197
  exports.listenHighestProfit = listenHighestProfit;
58343
59198
  exports.listenHighestProfitOnce = listenHighestProfitOnce;
59199
+ exports.listenIdlePing = listenIdlePing;
59200
+ exports.listenIdlePingOnce = listenIdlePingOnce;
58344
59201
  exports.listenMaxDrawdown = listenMaxDrawdown;
58345
59202
  exports.listenMaxDrawdownOnce = listenMaxDrawdownOnce;
58346
59203
  exports.listenPartialLossAvailable = listenPartialLossAvailable;