backtest-kit 2.3.2 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -5004,6 +5004,68 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5004
5004
  const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user");
5005
5005
  return { activated: false, cancelled: true, activationIndex: i, result };
5006
5006
  }
5007
+ // КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
5008
+ // Обрабатываем inline (как в tick()) с риск-проверкой по averagePrice
5009
+ if (self._activatedSignal) {
5010
+ const activatedSignal = self._activatedSignal;
5011
+ self._activatedSignal = null;
5012
+ // Check if strategy was stopped
5013
+ if (self._isStopped) {
5014
+ self.params.logger.info("ClientStrategy backtest user-activated signal cancelled (stopped)", {
5015
+ symbol: self.params.execution.context.symbol,
5016
+ signalId: activatedSignal.id,
5017
+ });
5018
+ await self.setScheduledSignal(null);
5019
+ return { activated: false, cancelled: false, activationIndex: i, result: null };
5020
+ }
5021
+ // Риск-проверка по averagePrice (симметрия с LIVE tick())
5022
+ if (await not(CALL_RISK_CHECK_SIGNAL_FN(self, self.params.execution.context.symbol, activatedSignal, averagePrice, candle.timestamp, self.params.execution.context.backtest))) {
5023
+ self.params.logger.info("ClientStrategy backtest user-activated signal rejected by risk", {
5024
+ symbol: self.params.execution.context.symbol,
5025
+ signalId: activatedSignal.id,
5026
+ });
5027
+ await self.setScheduledSignal(null);
5028
+ return { activated: false, cancelled: false, activationIndex: i, result: null };
5029
+ }
5030
+ await self.setScheduledSignal(null);
5031
+ const pendingSignal = {
5032
+ ...activatedSignal,
5033
+ pendingAt: candle.timestamp,
5034
+ _isScheduled: false,
5035
+ };
5036
+ await self.setPendingSignal(pendingSignal);
5037
+ await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
5038
+ // Emit commit AFTER successful risk check
5039
+ const publicSignalForCommit = TO_PUBLIC_SIGNAL(pendingSignal);
5040
+ await CALL_COMMIT_FN(self, {
5041
+ action: "activate-scheduled",
5042
+ symbol: self.params.execution.context.symbol,
5043
+ strategyName: self.params.strategyName,
5044
+ exchangeName: self.params.exchangeName,
5045
+ frameName: self.params.frameName,
5046
+ signalId: activatedSignal.id,
5047
+ backtest: self.params.execution.context.backtest,
5048
+ activateId: activatedSignal.activateId,
5049
+ timestamp: candle.timestamp,
5050
+ currentPrice: averagePrice,
5051
+ position: publicSignalForCommit.position,
5052
+ priceOpen: publicSignalForCommit.priceOpen,
5053
+ priceTakeProfit: publicSignalForCommit.priceTakeProfit,
5054
+ priceStopLoss: publicSignalForCommit.priceStopLoss,
5055
+ originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5056
+ originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
5057
+ scheduledAt: publicSignalForCommit.scheduledAt,
5058
+ pendingAt: publicSignalForCommit.pendingAt,
5059
+ });
5060
+ await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
5061
+ await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
5062
+ return {
5063
+ activated: true,
5064
+ cancelled: false,
5065
+ activationIndex: i,
5066
+ result: null,
5067
+ };
5068
+ }
5007
5069
  // КРИТИЧНО: Проверяем timeout ПЕРЕД проверкой цены
5008
5070
  const elapsedTime = candle.timestamp - scheduled.scheduledAt;
5009
5071
  if (elapsedTime >= maxTimeToWait) {
@@ -5226,6 +5288,7 @@ class ClientStrategy {
5226
5288
  this._scheduledSignal = null;
5227
5289
  this._cancelledSignal = null;
5228
5290
  this._closedSignal = null;
5291
+ this._activatedSignal = null;
5229
5292
  /** Queue for commit events to be processed in tick()/backtest() with proper timestamp */
5230
5293
  this._commitQueue = [];
5231
5294
  /**
@@ -5263,6 +5326,16 @@ class ClientStrategy {
5263
5326
  this.params.logger.debug("ClientStrategy setPendingSignal", {
5264
5327
  pendingSignal,
5265
5328
  });
5329
+ // КРИТИЧНО: Очищаем флаг закрытия при любом изменении pending signal
5330
+ // - при null: сигнал закрыт по TP/SL/timeout, флаг больше не нужен
5331
+ // - при новом сигнале: флаг от предыдущего сигнала не должен влиять на новый
5332
+ this._closedSignal = null;
5333
+ // ЗАЩИТА ИНВАРИАНТА: При установке нового pending сигнала очищаем scheduled
5334
+ // Не может быть одновременно pending И scheduled (взаимоисключающие состояния)
5335
+ // При null: scheduled может существовать (новый сигнал после закрытия позиции)
5336
+ if (pendingSignal !== null) {
5337
+ this._scheduledSignal = null;
5338
+ }
5266
5339
  this._pendingSignal = pendingSignal;
5267
5340
  // КРИТИЧНО: Всегда вызываем коллбек onWrite для тестирования persist storage
5268
5341
  // даже в backtest режиме, чтобы тесты могли перехватывать вызовы через mock adapter
@@ -5288,6 +5361,11 @@ class ClientStrategy {
5288
5361
  this.params.logger.debug("ClientStrategy setScheduledSignal", {
5289
5362
  scheduledSignal,
5290
5363
  });
5364
+ // КРИТИЧНО: Очищаем флаги отмены и активации при любом изменении scheduled signal
5365
+ // - при null: сигнал отменен/активирован по timeout/SL/user, флаги больше не нужны
5366
+ // - при новом сигнале: флаги от предыдущего сигнала не должны влиять на новый
5367
+ this._cancelledSignal = null;
5368
+ this._activatedSignal = null;
5291
5369
  this._scheduledSignal = scheduledSignal;
5292
5370
  if (this.params.execution.context.backtest) {
5293
5371
  return;
@@ -5477,12 +5555,8 @@ class ClientStrategy {
5477
5555
  const currentTime = this.params.execution.context.when.getTime();
5478
5556
  // Process queued commit events with proper timestamp
5479
5557
  await PROCESS_COMMIT_QUEUE_FN(this, currentTime);
5480
- // Early return if strategy was stopped
5481
- if (this._isStopped) {
5482
- const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5483
- return await RETURN_IDLE_FN(this, currentPrice);
5484
- }
5485
5558
  // Check if scheduled signal was cancelled - emit cancelled event once
5559
+ // NOTE: No _isStopped check here - cancellation must work for graceful shutdown
5486
5560
  if (this._cancelledSignal) {
5487
5561
  const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5488
5562
  const cancelledSignal = this._cancelledSignal;
@@ -5491,6 +5565,18 @@ class ClientStrategy {
5491
5565
  symbol: this.params.execution.context.symbol,
5492
5566
  signalId: cancelledSignal.id,
5493
5567
  });
5568
+ // Emit commit with correct timestamp from tick context
5569
+ await CALL_COMMIT_FN(this, {
5570
+ action: "cancel-scheduled",
5571
+ symbol: this.params.execution.context.symbol,
5572
+ strategyName: this.params.strategyName,
5573
+ exchangeName: this.params.exchangeName,
5574
+ frameName: this.params.frameName,
5575
+ signalId: cancelledSignal.id,
5576
+ backtest: this.params.execution.context.backtest,
5577
+ cancelId: cancelledSignal.cancelId,
5578
+ timestamp: currentTime,
5579
+ });
5494
5580
  // Call onCancel callback
5495
5581
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
5496
5582
  const result = {
@@ -5519,6 +5605,18 @@ class ClientStrategy {
5519
5605
  symbol: this.params.execution.context.symbol,
5520
5606
  signalId: closedSignal.id,
5521
5607
  });
5608
+ // Emit commit with correct timestamp from tick context
5609
+ await CALL_COMMIT_FN(this, {
5610
+ action: "close-pending",
5611
+ symbol: this.params.execution.context.symbol,
5612
+ strategyName: this.params.strategyName,
5613
+ exchangeName: this.params.exchangeName,
5614
+ frameName: this.params.frameName,
5615
+ signalId: closedSignal.id,
5616
+ backtest: this.params.execution.context.backtest,
5617
+ closeId: closedSignal.closeId,
5618
+ timestamp: currentTime,
5619
+ });
5522
5620
  // Call onClose callback
5523
5621
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
5524
5622
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -5545,6 +5643,78 @@ class ClientStrategy {
5545
5643
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5546
5644
  return result;
5547
5645
  }
5646
+ // Check if scheduled signal was activated - emit opened event once
5647
+ if (this._activatedSignal) {
5648
+ const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5649
+ const activatedSignal = this._activatedSignal;
5650
+ this._activatedSignal = null; // Clear after emitting
5651
+ this.params.logger.info("ClientStrategy tick: scheduled signal was activated", {
5652
+ symbol: this.params.execution.context.symbol,
5653
+ signalId: activatedSignal.id,
5654
+ });
5655
+ // Check if strategy was stopped (symmetry with backtest PROCESS_SCHEDULED_SIGNAL_CANDLES_FN)
5656
+ if (this._isStopped) {
5657
+ this.params.logger.info("ClientStrategy tick: user-activated signal cancelled (stopped)", {
5658
+ symbol: this.params.execution.context.symbol,
5659
+ signalId: activatedSignal.id,
5660
+ });
5661
+ await this.setScheduledSignal(null);
5662
+ return await RETURN_IDLE_FN(this, currentPrice);
5663
+ }
5664
+ // Check risk before activation
5665
+ if (await not(CALL_RISK_CHECK_SIGNAL_FN(this, this.params.execution.context.symbol, activatedSignal, currentPrice, currentTime, this.params.execution.context.backtest))) {
5666
+ this.params.logger.info("ClientStrategy tick: activated signal rejected by risk", {
5667
+ symbol: this.params.execution.context.symbol,
5668
+ signalId: activatedSignal.id,
5669
+ });
5670
+ return await RETURN_IDLE_FN(this, currentPrice);
5671
+ }
5672
+ // КРИТИЧЕСКИ ВАЖНО: обновляем pendingAt при активации
5673
+ const pendingSignal = {
5674
+ ...activatedSignal,
5675
+ pendingAt: currentTime,
5676
+ _isScheduled: false,
5677
+ };
5678
+ await this.setPendingSignal(pendingSignal);
5679
+ await CALL_RISK_ADD_SIGNAL_FN(this, this.params.execution.context.symbol, pendingSignal, currentTime, this.params.execution.context.backtest);
5680
+ // Emit commit AFTER successful risk check
5681
+ const publicSignalForCommit = TO_PUBLIC_SIGNAL(pendingSignal);
5682
+ await CALL_COMMIT_FN(this, {
5683
+ action: "activate-scheduled",
5684
+ symbol: this.params.execution.context.symbol,
5685
+ strategyName: this.params.strategyName,
5686
+ exchangeName: this.params.exchangeName,
5687
+ frameName: this.params.frameName,
5688
+ signalId: activatedSignal.id,
5689
+ backtest: this.params.execution.context.backtest,
5690
+ activateId: activatedSignal.activateId,
5691
+ timestamp: currentTime,
5692
+ currentPrice,
5693
+ position: publicSignalForCommit.position,
5694
+ priceOpen: publicSignalForCommit.priceOpen,
5695
+ priceTakeProfit: publicSignalForCommit.priceTakeProfit,
5696
+ priceStopLoss: publicSignalForCommit.priceStopLoss,
5697
+ originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5698
+ originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
5699
+ scheduledAt: publicSignalForCommit.scheduledAt,
5700
+ pendingAt: publicSignalForCommit.pendingAt,
5701
+ });
5702
+ // Call onOpen callback
5703
+ await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
5704
+ const result = {
5705
+ action: "opened",
5706
+ signal: TO_PUBLIC_SIGNAL(pendingSignal),
5707
+ strategyName: this.params.method.context.strategyName,
5708
+ exchangeName: this.params.method.context.exchangeName,
5709
+ frameName: this.params.method.context.frameName,
5710
+ symbol: this.params.execution.context.symbol,
5711
+ currentPrice,
5712
+ backtest: this.params.execution.context.backtest,
5713
+ createdAt: currentTime,
5714
+ };
5715
+ await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5716
+ return result;
5717
+ }
5548
5718
  // Monitor scheduled signal
5549
5719
  if (this._scheduledSignal && !this._pendingSignal) {
5550
5720
  const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
@@ -5568,7 +5738,12 @@ class ClientStrategy {
5568
5738
  return await RETURN_SCHEDULED_SIGNAL_ACTIVE_FN(this, this._scheduledSignal, currentPrice);
5569
5739
  }
5570
5740
  // Generate new signal if none exists
5741
+ // NOTE: _isStopped blocks NEW signal generation but allows existing positions to continue
5571
5742
  if (!this._pendingSignal && !this._scheduledSignal) {
5743
+ if (this._isStopped) {
5744
+ const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5745
+ return await RETURN_IDLE_FN(this, currentPrice);
5746
+ }
5572
5747
  const signal = await GET_SIGNAL_FN(this);
5573
5748
  if (signal) {
5574
5749
  if (signal._isScheduled === true) {
@@ -5642,6 +5817,18 @@ class ClientStrategy {
5642
5817
  const cancelledSignal = this._cancelledSignal;
5643
5818
  this._cancelledSignal = null; // Clear after using
5644
5819
  const closeTimestamp = this.params.execution.context.when.getTime();
5820
+ // Emit commit with correct timestamp from backtest context
5821
+ await CALL_COMMIT_FN(this, {
5822
+ action: "cancel-scheduled",
5823
+ symbol: this.params.execution.context.symbol,
5824
+ strategyName: this.params.strategyName,
5825
+ exchangeName: this.params.exchangeName,
5826
+ frameName: this.params.frameName,
5827
+ signalId: cancelledSignal.id,
5828
+ backtest: true,
5829
+ cancelId: cancelledSignal.cancelId,
5830
+ timestamp: closeTimestamp,
5831
+ });
5645
5832
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
5646
5833
  const cancelledResult = {
5647
5834
  action: "cancelled",
@@ -5667,6 +5854,18 @@ class ClientStrategy {
5667
5854
  const closedSignal = this._closedSignal;
5668
5855
  this._closedSignal = null; // Clear after using
5669
5856
  const closeTimestamp = this.params.execution.context.when.getTime();
5857
+ // Emit commit with correct timestamp from backtest context
5858
+ await CALL_COMMIT_FN(this, {
5859
+ action: "close-pending",
5860
+ symbol: this.params.execution.context.symbol,
5861
+ strategyName: this.params.strategyName,
5862
+ exchangeName: this.params.exchangeName,
5863
+ frameName: this.params.frameName,
5864
+ signalId: closedSignal.id,
5865
+ backtest: true,
5866
+ closeId: closedSignal.closeId,
5867
+ timestamp: closeTimestamp,
5868
+ });
5670
5869
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
5671
5870
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
5672
5871
  await CALL_PARTIAL_CLEAR_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
@@ -5827,8 +6026,18 @@ class ClientStrategy {
5827
6026
  symbol,
5828
6027
  hasPendingSignal: this._pendingSignal !== null,
5829
6028
  hasScheduledSignal: this._scheduledSignal !== null,
6029
+ hasActivatedSignal: this._activatedSignal !== null,
6030
+ hasCancelledSignal: this._cancelledSignal !== null,
6031
+ hasClosedSignal: this._closedSignal !== null,
5830
6032
  });
5831
6033
  this._isStopped = true;
6034
+ // Clear pending flags to start from clean state
6035
+ // NOTE: _isStopped blocks NEW position opening, but allows:
6036
+ // - cancelScheduled() / closePending() for graceful shutdown
6037
+ // - Monitoring existing _pendingSignal until TP/SL/timeout
6038
+ this._activatedSignal = null;
6039
+ this._cancelledSignal = null;
6040
+ this._closedSignal = null;
5832
6041
  // Clear scheduled signal if exists
5833
6042
  if (!this._scheduledSignal) {
5834
6043
  return;
@@ -5866,8 +6075,9 @@ class ClientStrategy {
5866
6075
  hasScheduledSignal: this._scheduledSignal !== null,
5867
6076
  cancelId,
5868
6077
  });
5869
- // Save cancelled signal for next tick to emit cancelled event
5870
- const hadScheduledSignal = this._scheduledSignal !== null;
6078
+ // NOTE: No _isStopped check - cancellation must work for graceful shutdown
6079
+ // (cancelling scheduled signal is not opening new position)
6080
+ // Save cancelled signal for next tick/backtest to emit cancelled event with correct timestamp
5871
6081
  if (this._scheduledSignal) {
5872
6082
  this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
5873
6083
  cancelId,
@@ -5875,37 +6085,60 @@ class ClientStrategy {
5875
6085
  this._scheduledSignal = null;
5876
6086
  }
5877
6087
  if (backtest) {
5878
- // Emit commit event only if signal was actually cancelled
5879
- if (hadScheduledSignal) {
5880
- await CALL_COMMIT_FN(this, {
5881
- action: "cancel-scheduled",
5882
- symbol,
5883
- strategyName: this.params.strategyName,
5884
- exchangeName: this.params.exchangeName,
5885
- frameName: this.params.frameName,
5886
- signalId: this._cancelledSignal.id,
5887
- backtest,
5888
- cancelId,
5889
- timestamp: this.params.execution.context.when.getTime(),
5890
- });
5891
- }
6088
+ // Commit will be emitted in backtest() with correct candle timestamp
5892
6089
  return;
5893
6090
  }
5894
6091
  await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.method.context.strategyName, this.params.method.context.exchangeName);
5895
- // Emit commit event only if signal was actually cancelled
5896
- if (hadScheduledSignal) {
5897
- await CALL_COMMIT_FN(this, {
5898
- action: "cancel-scheduled",
6092
+ // Commit will be emitted in tick() with correct currentTime
6093
+ }
6094
+ /**
6095
+ * Activates the scheduled signal without waiting for price to reach priceOpen.
6096
+ *
6097
+ * Forces immediate activation of the scheduled signal at the current price.
6098
+ * Does NOT affect active pending signals or strategy operation.
6099
+ * Does NOT set stop flag - strategy can continue generating new signals.
6100
+ *
6101
+ * Use case: User-initiated early activation of a scheduled entry.
6102
+ *
6103
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
6104
+ * @param backtest - Whether running in backtest mode
6105
+ * @param activateId - Optional identifier for this activation operation
6106
+ * @returns Promise that resolves when scheduled signal is activated
6107
+ *
6108
+ * @example
6109
+ * ```typescript
6110
+ * // Activate scheduled signal without waiting for priceOpen
6111
+ * await strategy.activateScheduled("BTCUSDT", false, "user-activate-123");
6112
+ * // Scheduled signal becomes pending signal immediately
6113
+ * ```
6114
+ */
6115
+ async activateScheduled(symbol, backtest, activateId) {
6116
+ this.params.logger.debug("ClientStrategy activateScheduled", {
6117
+ symbol,
6118
+ hasScheduledSignal: this._scheduledSignal !== null,
6119
+ activateId,
6120
+ });
6121
+ // Block activation if strategy stopped - activation = opening NEW position
6122
+ // (unlike cancelScheduled/closePending which handle existing signals for graceful shutdown)
6123
+ if (this._isStopped) {
6124
+ this.params.logger.debug("ClientStrategy activateScheduled: strategy stopped, skipping", {
5899
6125
  symbol,
5900
- strategyName: this.params.strategyName,
5901
- exchangeName: this.params.exchangeName,
5902
- frameName: this.params.frameName,
5903
- signalId: this._cancelledSignal.id,
5904
- backtest,
5905
- cancelId,
5906
- timestamp: this.params.execution.context.when.getTime(),
5907
6126
  });
6127
+ return;
6128
+ }
6129
+ // Save activated signal for next tick to emit opened event
6130
+ if (this._scheduledSignal) {
6131
+ this._activatedSignal = Object.assign({}, this._scheduledSignal, {
6132
+ activateId,
6133
+ });
6134
+ this._scheduledSignal = null;
6135
+ }
6136
+ if (backtest) {
6137
+ // Commit will be emitted AFTER successful risk check in PROCESS_SCHEDULED_SIGNAL_CANDLES_FN
6138
+ return;
5908
6139
  }
6140
+ await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.method.context.strategyName, this.params.method.context.exchangeName);
6141
+ // Commit will be emitted AFTER successful risk check in tick()
5909
6142
  }
5910
6143
  /**
5911
6144
  * Closes the pending signal without stopping the strategy.
@@ -5934,8 +6167,8 @@ class ClientStrategy {
5934
6167
  hasPendingSignal: this._pendingSignal !== null,
5935
6168
  closeId,
5936
6169
  });
5937
- // Save closed signal for next tick to emit closed event
5938
- const hadPendingSignal = this._pendingSignal !== null;
6170
+ // NOTE: No _isStopped check - closing position must work for graceful shutdown
6171
+ // Save closed signal for next tick/backtest to emit closed event with correct timestamp
5939
6172
  if (this._pendingSignal) {
5940
6173
  this._closedSignal = Object.assign({}, this._pendingSignal, {
5941
6174
  closeId,
@@ -5943,37 +6176,11 @@ class ClientStrategy {
5943
6176
  this._pendingSignal = null;
5944
6177
  }
5945
6178
  if (backtest) {
5946
- // Emit commit event only if signal was actually closed
5947
- if (hadPendingSignal) {
5948
- await CALL_COMMIT_FN(this, {
5949
- action: "close-pending",
5950
- symbol,
5951
- strategyName: this.params.strategyName,
5952
- exchangeName: this.params.exchangeName,
5953
- frameName: this.params.frameName,
5954
- signalId: this._closedSignal.id,
5955
- backtest,
5956
- closeId,
5957
- timestamp: this.params.execution.context.when.getTime(),
5958
- });
5959
- }
6179
+ // Commit will be emitted in backtest() with correct candle timestamp
5960
6180
  return;
5961
6181
  }
5962
6182
  await PersistSignalAdapter.writeSignalData(this._pendingSignal, symbol, this.params.strategyName, this.params.exchangeName);
5963
- // Emit commit event only if signal was actually closed
5964
- if (hadPendingSignal) {
5965
- await CALL_COMMIT_FN(this, {
5966
- action: "close-pending",
5967
- symbol,
5968
- strategyName: this.params.strategyName,
5969
- exchangeName: this.params.exchangeName,
5970
- frameName: this.params.frameName,
5971
- signalId: this._closedSignal.id,
5972
- backtest,
5973
- closeId,
5974
- timestamp: this.params.execution.context.when.getTime(),
5975
- });
5976
- }
6183
+ // Commit will be emitted in tick() with correct currentTime
5977
6184
  }
5978
6185
  /**
5979
6186
  * Executes partial close at profit level (moving toward TP).
@@ -7812,6 +8019,39 @@ class StrategyConnectionService {
7812
8019
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
7813
8020
  return await strategy.breakeven(symbol, currentPrice, backtest);
7814
8021
  };
8022
+ /**
8023
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
8024
+ *
8025
+ * Delegates to ClientStrategy.activateScheduled() which sets _activatedSignal flag.
8026
+ * The actual activation happens on next tick() when strategy detects the flag.
8027
+ *
8028
+ * @param backtest - Whether running in backtest mode
8029
+ * @param symbol - Trading pair symbol
8030
+ * @param context - Execution context with strategyName, exchangeName, frameName
8031
+ * @param activateId - Optional identifier for the activation reason
8032
+ * @returns Promise that resolves when activation flag is set
8033
+ *
8034
+ * @example
8035
+ * ```typescript
8036
+ * // Activate scheduled signal early
8037
+ * await strategyConnectionService.activateScheduled(
8038
+ * false,
8039
+ * "BTCUSDT",
8040
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
8041
+ * "manual-activation"
8042
+ * );
8043
+ * ```
8044
+ */
8045
+ this.activateScheduled = async (backtest, symbol, context, activateId) => {
8046
+ this.loggerService.log("strategyConnectionService activateScheduled", {
8047
+ symbol,
8048
+ context,
8049
+ backtest,
8050
+ activateId,
8051
+ });
8052
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
8053
+ return await strategy.activateScheduled(symbol, backtest, activateId);
8054
+ };
7815
8055
  }
7816
8056
  }
7817
8057
 
@@ -11124,6 +11364,39 @@ class StrategyCoreService {
11124
11364
  await this.validate(context);
11125
11365
  return await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
11126
11366
  };
11367
+ /**
11368
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
11369
+ *
11370
+ * Validates strategy existence and delegates to connection service
11371
+ * to set the activation flag. The actual activation happens on next tick().
11372
+ *
11373
+ * @param backtest - Whether running in backtest mode
11374
+ * @param symbol - Trading pair symbol
11375
+ * @param context - Execution context with strategyName, exchangeName, frameName
11376
+ * @param activateId - Optional identifier for the activation reason
11377
+ * @returns Promise that resolves when activation flag is set
11378
+ *
11379
+ * @example
11380
+ * ```typescript
11381
+ * // Activate scheduled signal early
11382
+ * await strategyCoreService.activateScheduled(
11383
+ * false,
11384
+ * "BTCUSDT",
11385
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
11386
+ * "manual-activation"
11387
+ * );
11388
+ * ```
11389
+ */
11390
+ this.activateScheduled = async (backtest, symbol, context, activateId) => {
11391
+ this.loggerService.log("strategyCoreService activateScheduled", {
11392
+ symbol,
11393
+ context,
11394
+ backtest,
11395
+ activateId,
11396
+ });
11397
+ await this.validate(context);
11398
+ return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
11399
+ };
11127
11400
  }
11128
11401
  }
11129
11402
 
@@ -12403,6 +12676,8 @@ class WalkerSchemaService {
12403
12676
  }
12404
12677
  }
12405
12678
 
12679
+ const ACTIVE_CANDLE_INCLUDED = 1;
12680
+ const SCHEDULE_ACTIVATION_CANDLE_SKIP = 1;
12406
12681
  /**
12407
12682
  * Private service for backtest orchestration using async generators.
12408
12683
  *
@@ -12526,9 +12801,9 @@ class BacktestLogicPrivateService {
12526
12801
  // - CC_SCHEDULE_AWAIT_MINUTES для ожидания активации
12527
12802
  // - minuteEstimatedTime для работы сигнала ПОСЛЕ активации
12528
12803
  // - +1 потому что when включается как первая свеча
12529
- const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - 1;
12804
+ const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - ACTIVE_CANDLE_INCLUDED;
12530
12805
  const bufferStartTime = new Date(when.getTime() - bufferMinutes * 60 * 1000);
12531
- const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
12806
+ const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + SCHEDULE_ACTIVATION_CANDLE_SKIP;
12532
12807
  let candles;
12533
12808
  try {
12534
12809
  candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
@@ -12666,7 +12941,7 @@ class BacktestLogicPrivateService {
12666
12941
  // КРИТИЧНО: Получаем свечи включая буфер для VWAP
12667
12942
  // Сдвигаем начало назад на CC_AVG_PRICE_CANDLES_COUNT-1 минут для буфера VWAP
12668
12943
  // Запрашиваем minuteEstimatedTime + буфер свечей одним запросом
12669
- const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - 1;
12944
+ const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - ACTIVE_CANDLE_INCLUDED;
12670
12945
  const bufferStartTime = new Date(when.getTime() - bufferMinutes * 60 * 1000);
12671
12946
  const totalCandles = signal.minuteEstimatedTime + GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT;
12672
12947
  let candles;
@@ -24356,6 +24631,67 @@ class StrategyReportService {
24356
24631
  walkerName: "",
24357
24632
  });
24358
24633
  };
24634
+ /**
24635
+ * Logs an activate-scheduled event when a scheduled signal is activated early.
24636
+ *
24637
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24638
+ * @param currentPrice - Current market price at time of activation
24639
+ * @param isBacktest - Whether this is a backtest or live trading event
24640
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24641
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
24642
+ * @param position - Trade direction: "long" or "short"
24643
+ * @param priceOpen - Entry price for the position
24644
+ * @param priceTakeProfit - Effective take profit price
24645
+ * @param priceStopLoss - Effective stop loss price
24646
+ * @param originalPriceTakeProfit - Original take profit before trailing
24647
+ * @param originalPriceStopLoss - Original stop loss before trailing
24648
+ * @param scheduledAt - Signal creation timestamp in milliseconds
24649
+ * @param pendingAt - Pending timestamp in milliseconds
24650
+ * @param activateId - Optional identifier for the activation reason
24651
+ */
24652
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
24653
+ this.loggerService.log("strategyReportService activateScheduled", {
24654
+ symbol,
24655
+ currentPrice,
24656
+ isBacktest,
24657
+ activateId,
24658
+ });
24659
+ if (!this.subscribe.hasValue()) {
24660
+ return;
24661
+ }
24662
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
24663
+ exchangeName: context.exchangeName,
24664
+ strategyName: context.strategyName,
24665
+ frameName: context.frameName,
24666
+ });
24667
+ if (!scheduledRow) {
24668
+ return;
24669
+ }
24670
+ const createdAt = new Date(timestamp).toISOString();
24671
+ await Report.writeData("strategy", {
24672
+ action: "activate-scheduled",
24673
+ activateId,
24674
+ currentPrice,
24675
+ symbol,
24676
+ timestamp,
24677
+ createdAt,
24678
+ position,
24679
+ priceOpen,
24680
+ priceTakeProfit,
24681
+ priceStopLoss,
24682
+ originalPriceTakeProfit,
24683
+ originalPriceStopLoss,
24684
+ scheduledAt,
24685
+ pendingAt,
24686
+ }, {
24687
+ signalId: scheduledRow.id,
24688
+ exchangeName: context.exchangeName,
24689
+ frameName: context.frameName,
24690
+ strategyName: context.strategyName,
24691
+ symbol,
24692
+ walkerName: "",
24693
+ });
24694
+ };
24359
24695
  /**
24360
24696
  * Initializes the service for event logging.
24361
24697
  *
@@ -24415,7 +24751,14 @@ class StrategyReportService {
24415
24751
  frameName: event.frameName,
24416
24752
  strategyName: event.strategyName,
24417
24753
  }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
24418
- const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
24754
+ const unActivateScheduled = strategyCommitSubject
24755
+ .filter(({ action }) => action === "activate-scheduled")
24756
+ .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
24757
+ exchangeName: event.exchangeName,
24758
+ frameName: event.frameName,
24759
+ strategyName: event.strategyName,
24760
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
24761
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
24419
24762
  return () => {
24420
24763
  disposeFn();
24421
24764
  this.subscribe.clear();
@@ -24546,6 +24889,7 @@ class ReportStorage {
24546
24889
  trailingStopCount: 0,
24547
24890
  trailingTakeCount: 0,
24548
24891
  breakevenCount: 0,
24892
+ activateScheduledCount: 0,
24549
24893
  };
24550
24894
  }
24551
24895
  return {
@@ -24558,6 +24902,7 @@ class ReportStorage {
24558
24902
  trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
24559
24903
  trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
24560
24904
  breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
24905
+ activateScheduledCount: this._eventList.filter(e => e.action === "activate-scheduled").length,
24561
24906
  };
24562
24907
  }
24563
24908
  /**
@@ -24606,6 +24951,7 @@ class ReportStorage {
24606
24951
  `- Trailing stop: ${stats.trailingStopCount}`,
24607
24952
  `- Trailing take: ${stats.trailingTakeCount}`,
24608
24953
  `- Breakeven: ${stats.breakevenCount}`,
24954
+ `- Activate scheduled: ${stats.activateScheduledCount}`,
24609
24955
  ].join("\n");
24610
24956
  }
24611
24957
  /**
@@ -25072,6 +25418,66 @@ class StrategyMarkdownService {
25072
25418
  pendingAt,
25073
25419
  });
25074
25420
  };
25421
+ /**
25422
+ * Records an activate-scheduled event when a scheduled signal is activated early.
25423
+ *
25424
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
25425
+ * @param currentPrice - Current market price at time of activation
25426
+ * @param isBacktest - Whether this is a backtest or live trading event
25427
+ * @param context - Strategy context with strategyName, exchangeName, frameName
25428
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
25429
+ * @param position - Trade direction: "long" or "short"
25430
+ * @param priceOpen - Entry price for the position
25431
+ * @param priceTakeProfit - Effective take profit price
25432
+ * @param priceStopLoss - Effective stop loss price
25433
+ * @param originalPriceTakeProfit - Original take profit before trailing
25434
+ * @param originalPriceStopLoss - Original stop loss before trailing
25435
+ * @param scheduledAt - Signal creation timestamp in milliseconds
25436
+ * @param pendingAt - Pending timestamp in milliseconds
25437
+ * @param activateId - Optional identifier for the activation reason
25438
+ */
25439
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
25440
+ this.loggerService.log("strategyMarkdownService activateScheduled", {
25441
+ symbol,
25442
+ currentPrice,
25443
+ isBacktest,
25444
+ activateId,
25445
+ });
25446
+ if (!this.subscribe.hasValue()) {
25447
+ return;
25448
+ }
25449
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
25450
+ exchangeName: context.exchangeName,
25451
+ strategyName: context.strategyName,
25452
+ frameName: context.frameName,
25453
+ });
25454
+ if (!scheduledRow) {
25455
+ return;
25456
+ }
25457
+ const createdAt = new Date(timestamp).toISOString();
25458
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
25459
+ storage.addEvent({
25460
+ timestamp,
25461
+ symbol,
25462
+ strategyName: context.strategyName,
25463
+ exchangeName: context.exchangeName,
25464
+ frameName: context.frameName,
25465
+ signalId: scheduledRow.id,
25466
+ action: "activate-scheduled",
25467
+ activateId,
25468
+ currentPrice,
25469
+ createdAt,
25470
+ backtest: isBacktest,
25471
+ position,
25472
+ priceOpen,
25473
+ priceTakeProfit,
25474
+ priceStopLoss,
25475
+ originalPriceTakeProfit,
25476
+ originalPriceStopLoss,
25477
+ scheduledAt,
25478
+ pendingAt,
25479
+ });
25480
+ };
25075
25481
  /**
25076
25482
  * Retrieves aggregated statistics from accumulated strategy events.
25077
25483
  *
@@ -25245,7 +25651,14 @@ class StrategyMarkdownService {
25245
25651
  frameName: event.frameName,
25246
25652
  strategyName: event.strategyName,
25247
25653
  }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25248
- const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
25654
+ const unActivateScheduled = strategyCommitSubject
25655
+ .filter(({ action }) => action === "activate-scheduled")
25656
+ .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
25657
+ exchangeName: event.exchangeName,
25658
+ frameName: event.frameName,
25659
+ strategyName: event.strategyName,
25660
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
25661
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
25249
25662
  return () => {
25250
25663
  disposeFn();
25251
25664
  this.subscribe.clear();
@@ -26171,6 +26584,7 @@ const PARTIAL_LOSS_METHOD_NAME = "strategy.commitPartialLoss";
26171
26584
  const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
26172
26585
  const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
26173
26586
  const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
26587
+ const ACTIVATE_SCHEDULED_METHOD_NAME = "strategy.commitActivateScheduled";
26174
26588
  /**
26175
26589
  * Cancels the scheduled signal without stopping the strategy.
26176
26590
  *
@@ -26488,6 +26902,41 @@ async function commitBreakeven(symbol) {
26488
26902
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
26489
26903
  return await bt.strategyCoreService.breakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
26490
26904
  }
26905
+ /**
26906
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
26907
+ *
26908
+ * Sets the activation flag on the scheduled signal. The actual activation
26909
+ * happens on the next tick() when strategy detects the flag.
26910
+ *
26911
+ * Automatically detects backtest/live mode from execution context.
26912
+ *
26913
+ * @param symbol - Trading pair symbol
26914
+ * @param activateId - Optional activation ID for tracking user-initiated activations
26915
+ * @returns Promise that resolves when activation flag is set
26916
+ *
26917
+ * @example
26918
+ * ```typescript
26919
+ * import { commitActivateScheduled } from "backtest-kit";
26920
+ *
26921
+ * // Activate scheduled signal early with custom ID
26922
+ * await commitActivateScheduled("BTCUSDT", "manual-activate-001");
26923
+ * ```
26924
+ */
26925
+ async function commitActivateScheduled(symbol, activateId) {
26926
+ bt.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
26927
+ symbol,
26928
+ activateId,
26929
+ });
26930
+ if (!ExecutionContextService.hasContext()) {
26931
+ throw new Error("commitActivateScheduled requires an execution context");
26932
+ }
26933
+ if (!MethodContextService.hasContext()) {
26934
+ throw new Error("commitActivateScheduled requires a method context");
26935
+ }
26936
+ const { backtest: isBacktest } = bt.executionContextService.context;
26937
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
26938
+ await bt.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
26939
+ }
26491
26940
 
26492
26941
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
26493
26942
  /**
@@ -28658,6 +29107,7 @@ const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.commitPartialProfit";
28658
29107
  const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
28659
29108
  const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
28660
29109
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
29110
+ const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
28661
29111
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
28662
29112
  /**
28663
29113
  * Internal task function that runs backtest and handles completion.
@@ -29514,6 +29964,46 @@ class BacktestUtils {
29514
29964
  }
29515
29965
  return await bt.strategyCoreService.breakeven(true, symbol, currentPrice, context);
29516
29966
  };
29967
+ /**
29968
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
29969
+ *
29970
+ * Sets the activation flag on the scheduled signal. The actual activation
29971
+ * happens on the next tick() when strategy detects the flag.
29972
+ *
29973
+ * @param symbol - Trading pair symbol
29974
+ * @param context - Execution context with strategyName, exchangeName, and frameName
29975
+ * @param activateId - Optional activation ID for tracking user-initiated activations
29976
+ * @returns Promise that resolves when activation flag is set
29977
+ *
29978
+ * @example
29979
+ * ```typescript
29980
+ * // Activate scheduled signal early with custom ID
29981
+ * await Backtest.commitActivateScheduled("BTCUSDT", {
29982
+ * strategyName: "my-strategy",
29983
+ * exchangeName: "binance",
29984
+ * frameName: "1h"
29985
+ * }, "manual-activate-001");
29986
+ * ```
29987
+ */
29988
+ this.commitActivateScheduled = async (symbol, context, activateId) => {
29989
+ bt.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
29990
+ symbol,
29991
+ context,
29992
+ activateId,
29993
+ });
29994
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
29995
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
29996
+ {
29997
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
29998
+ riskName &&
29999
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
30000
+ riskList &&
30001
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
30002
+ actions &&
30003
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
30004
+ }
30005
+ await bt.strategyCoreService.activateScheduled(true, symbol, context, activateId);
30006
+ };
29517
30007
  /**
29518
30008
  * Gets statistical data from all closed signals for a symbol-strategy pair.
29519
30009
  *
@@ -29689,6 +30179,7 @@ const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.commitPartialProfit";
29689
30179
  const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
29690
30180
  const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
29691
30181
  const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
30182
+ const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
29692
30183
  /**
29693
30184
  * Internal task function that runs live trading and handles completion.
29694
30185
  * Consumes live trading results and updates instance state flags.
@@ -30513,6 +31004,46 @@ class LiveUtils {
30513
31004
  frameName: "",
30514
31005
  });
30515
31006
  };
31007
+ /**
31008
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
31009
+ *
31010
+ * Sets the activation flag on the scheduled signal. The actual activation
31011
+ * happens on the next tick() when strategy detects the flag.
31012
+ *
31013
+ * @param symbol - Trading pair symbol
31014
+ * @param context - Execution context with strategyName and exchangeName
31015
+ * @param activateId - Optional activation ID for tracking user-initiated activations
31016
+ * @returns Promise that resolves when activation flag is set
31017
+ *
31018
+ * @example
31019
+ * ```typescript
31020
+ * // Activate scheduled signal early with custom ID
31021
+ * await Live.commitActivateScheduled("BTCUSDT", {
31022
+ * strategyName: "my-strategy",
31023
+ * exchangeName: "binance"
31024
+ * }, "manual-activate-001");
31025
+ * ```
31026
+ */
31027
+ this.commitActivateScheduled = async (symbol, context, activateId) => {
31028
+ bt.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
31029
+ symbol,
31030
+ context,
31031
+ activateId,
31032
+ });
31033
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
31034
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
31035
+ {
31036
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
31037
+ riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
31038
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
31039
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
31040
+ }
31041
+ await bt.strategyCoreService.activateScheduled(false, symbol, {
31042
+ strategyName: context.strategyName,
31043
+ exchangeName: context.exchangeName,
31044
+ frameName: "",
31045
+ }, activateId);
31046
+ };
30516
31047
  /**
30517
31048
  * Gets statistical data from all live trading events for a symbol-strategy pair.
30518
31049
  *
@@ -34448,6 +34979,29 @@ class NotificationInstance {
34448
34979
  createdAt: data.timestamp,
34449
34980
  });
34450
34981
  }
34982
+ else if (data.action === "activate-scheduled") {
34983
+ this._addNotification({
34984
+ type: "activate_scheduled.commit",
34985
+ id: CREATE_KEY_FN(),
34986
+ timestamp: data.timestamp,
34987
+ backtest: data.backtest,
34988
+ symbol: data.symbol,
34989
+ strategyName: data.strategyName,
34990
+ exchangeName: data.exchangeName,
34991
+ signalId: data.signalId,
34992
+ activateId: data.activateId,
34993
+ currentPrice: data.currentPrice,
34994
+ position: data.position,
34995
+ priceOpen: data.priceOpen,
34996
+ priceTakeProfit: data.priceTakeProfit,
34997
+ priceStopLoss: data.priceStopLoss,
34998
+ originalPriceTakeProfit: data.originalPriceTakeProfit,
34999
+ originalPriceStopLoss: data.originalPriceStopLoss,
35000
+ scheduledAt: data.scheduledAt,
35001
+ pendingAt: data.pendingAt,
35002
+ createdAt: data.timestamp,
35003
+ });
35004
+ }
34451
35005
  };
34452
35006
  /**
34453
35007
  * Processes risk rejection events.
@@ -35252,4 +35806,4 @@ const set = (object, path, value) => {
35252
35806
  }
35253
35807
  };
35254
35808
 
35255
- export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };
35809
+ export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate };