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.cjs CHANGED
@@ -5024,6 +5024,68 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5024
5024
  const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user");
5025
5025
  return { activated: false, cancelled: true, activationIndex: i, result };
5026
5026
  }
5027
+ // КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
5028
+ // Обрабатываем inline (как в tick()) с риск-проверкой по averagePrice
5029
+ if (self._activatedSignal) {
5030
+ const activatedSignal = self._activatedSignal;
5031
+ self._activatedSignal = null;
5032
+ // Check if strategy was stopped
5033
+ if (self._isStopped) {
5034
+ self.params.logger.info("ClientStrategy backtest user-activated signal cancelled (stopped)", {
5035
+ symbol: self.params.execution.context.symbol,
5036
+ signalId: activatedSignal.id,
5037
+ });
5038
+ await self.setScheduledSignal(null);
5039
+ return { activated: false, cancelled: false, activationIndex: i, result: null };
5040
+ }
5041
+ // Риск-проверка по averagePrice (симметрия с LIVE tick())
5042
+ if (await functoolsKit.not(CALL_RISK_CHECK_SIGNAL_FN(self, self.params.execution.context.symbol, activatedSignal, averagePrice, candle.timestamp, self.params.execution.context.backtest))) {
5043
+ self.params.logger.info("ClientStrategy backtest user-activated signal rejected by risk", {
5044
+ symbol: self.params.execution.context.symbol,
5045
+ signalId: activatedSignal.id,
5046
+ });
5047
+ await self.setScheduledSignal(null);
5048
+ return { activated: false, cancelled: false, activationIndex: i, result: null };
5049
+ }
5050
+ await self.setScheduledSignal(null);
5051
+ const pendingSignal = {
5052
+ ...activatedSignal,
5053
+ pendingAt: candle.timestamp,
5054
+ _isScheduled: false,
5055
+ };
5056
+ await self.setPendingSignal(pendingSignal);
5057
+ await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
5058
+ // Emit commit AFTER successful risk check
5059
+ const publicSignalForCommit = TO_PUBLIC_SIGNAL(pendingSignal);
5060
+ await CALL_COMMIT_FN(self, {
5061
+ action: "activate-scheduled",
5062
+ symbol: self.params.execution.context.symbol,
5063
+ strategyName: self.params.strategyName,
5064
+ exchangeName: self.params.exchangeName,
5065
+ frameName: self.params.frameName,
5066
+ signalId: activatedSignal.id,
5067
+ backtest: self.params.execution.context.backtest,
5068
+ activateId: activatedSignal.activateId,
5069
+ timestamp: candle.timestamp,
5070
+ currentPrice: averagePrice,
5071
+ position: publicSignalForCommit.position,
5072
+ priceOpen: publicSignalForCommit.priceOpen,
5073
+ priceTakeProfit: publicSignalForCommit.priceTakeProfit,
5074
+ priceStopLoss: publicSignalForCommit.priceStopLoss,
5075
+ originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5076
+ originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
5077
+ scheduledAt: publicSignalForCommit.scheduledAt,
5078
+ pendingAt: publicSignalForCommit.pendingAt,
5079
+ });
5080
+ await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
5081
+ await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
5082
+ return {
5083
+ activated: true,
5084
+ cancelled: false,
5085
+ activationIndex: i,
5086
+ result: null,
5087
+ };
5088
+ }
5027
5089
  // КРИТИЧНО: Проверяем timeout ПЕРЕД проверкой цены
5028
5090
  const elapsedTime = candle.timestamp - scheduled.scheduledAt;
5029
5091
  if (elapsedTime >= maxTimeToWait) {
@@ -5246,6 +5308,7 @@ class ClientStrategy {
5246
5308
  this._scheduledSignal = null;
5247
5309
  this._cancelledSignal = null;
5248
5310
  this._closedSignal = null;
5311
+ this._activatedSignal = null;
5249
5312
  /** Queue for commit events to be processed in tick()/backtest() with proper timestamp */
5250
5313
  this._commitQueue = [];
5251
5314
  /**
@@ -5283,6 +5346,16 @@ class ClientStrategy {
5283
5346
  this.params.logger.debug("ClientStrategy setPendingSignal", {
5284
5347
  pendingSignal,
5285
5348
  });
5349
+ // КРИТИЧНО: Очищаем флаг закрытия при любом изменении pending signal
5350
+ // - при null: сигнал закрыт по TP/SL/timeout, флаг больше не нужен
5351
+ // - при новом сигнале: флаг от предыдущего сигнала не должен влиять на новый
5352
+ this._closedSignal = null;
5353
+ // ЗАЩИТА ИНВАРИАНТА: При установке нового pending сигнала очищаем scheduled
5354
+ // Не может быть одновременно pending И scheduled (взаимоисключающие состояния)
5355
+ // При null: scheduled может существовать (новый сигнал после закрытия позиции)
5356
+ if (pendingSignal !== null) {
5357
+ this._scheduledSignal = null;
5358
+ }
5286
5359
  this._pendingSignal = pendingSignal;
5287
5360
  // КРИТИЧНО: Всегда вызываем коллбек onWrite для тестирования persist storage
5288
5361
  // даже в backtest режиме, чтобы тесты могли перехватывать вызовы через mock adapter
@@ -5308,6 +5381,11 @@ class ClientStrategy {
5308
5381
  this.params.logger.debug("ClientStrategy setScheduledSignal", {
5309
5382
  scheduledSignal,
5310
5383
  });
5384
+ // КРИТИЧНО: Очищаем флаги отмены и активации при любом изменении scheduled signal
5385
+ // - при null: сигнал отменен/активирован по timeout/SL/user, флаги больше не нужны
5386
+ // - при новом сигнале: флаги от предыдущего сигнала не должны влиять на новый
5387
+ this._cancelledSignal = null;
5388
+ this._activatedSignal = null;
5311
5389
  this._scheduledSignal = scheduledSignal;
5312
5390
  if (this.params.execution.context.backtest) {
5313
5391
  return;
@@ -5497,12 +5575,8 @@ class ClientStrategy {
5497
5575
  const currentTime = this.params.execution.context.when.getTime();
5498
5576
  // Process queued commit events with proper timestamp
5499
5577
  await PROCESS_COMMIT_QUEUE_FN(this, currentTime);
5500
- // Early return if strategy was stopped
5501
- if (this._isStopped) {
5502
- const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5503
- return await RETURN_IDLE_FN(this, currentPrice);
5504
- }
5505
5578
  // Check if scheduled signal was cancelled - emit cancelled event once
5579
+ // NOTE: No _isStopped check here - cancellation must work for graceful shutdown
5506
5580
  if (this._cancelledSignal) {
5507
5581
  const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5508
5582
  const cancelledSignal = this._cancelledSignal;
@@ -5511,6 +5585,18 @@ class ClientStrategy {
5511
5585
  symbol: this.params.execution.context.symbol,
5512
5586
  signalId: cancelledSignal.id,
5513
5587
  });
5588
+ // Emit commit with correct timestamp from tick context
5589
+ await CALL_COMMIT_FN(this, {
5590
+ action: "cancel-scheduled",
5591
+ symbol: this.params.execution.context.symbol,
5592
+ strategyName: this.params.strategyName,
5593
+ exchangeName: this.params.exchangeName,
5594
+ frameName: this.params.frameName,
5595
+ signalId: cancelledSignal.id,
5596
+ backtest: this.params.execution.context.backtest,
5597
+ cancelId: cancelledSignal.cancelId,
5598
+ timestamp: currentTime,
5599
+ });
5514
5600
  // Call onCancel callback
5515
5601
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
5516
5602
  const result = {
@@ -5539,6 +5625,18 @@ class ClientStrategy {
5539
5625
  symbol: this.params.execution.context.symbol,
5540
5626
  signalId: closedSignal.id,
5541
5627
  });
5628
+ // Emit commit with correct timestamp from tick context
5629
+ await CALL_COMMIT_FN(this, {
5630
+ action: "close-pending",
5631
+ symbol: this.params.execution.context.symbol,
5632
+ strategyName: this.params.strategyName,
5633
+ exchangeName: this.params.exchangeName,
5634
+ frameName: this.params.frameName,
5635
+ signalId: closedSignal.id,
5636
+ backtest: this.params.execution.context.backtest,
5637
+ closeId: closedSignal.closeId,
5638
+ timestamp: currentTime,
5639
+ });
5542
5640
  // Call onClose callback
5543
5641
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
5544
5642
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -5565,6 +5663,78 @@ class ClientStrategy {
5565
5663
  await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5566
5664
  return result;
5567
5665
  }
5666
+ // Check if scheduled signal was activated - emit opened event once
5667
+ if (this._activatedSignal) {
5668
+ const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5669
+ const activatedSignal = this._activatedSignal;
5670
+ this._activatedSignal = null; // Clear after emitting
5671
+ this.params.logger.info("ClientStrategy tick: scheduled signal was activated", {
5672
+ symbol: this.params.execution.context.symbol,
5673
+ signalId: activatedSignal.id,
5674
+ });
5675
+ // Check if strategy was stopped (symmetry with backtest PROCESS_SCHEDULED_SIGNAL_CANDLES_FN)
5676
+ if (this._isStopped) {
5677
+ this.params.logger.info("ClientStrategy tick: user-activated signal cancelled (stopped)", {
5678
+ symbol: this.params.execution.context.symbol,
5679
+ signalId: activatedSignal.id,
5680
+ });
5681
+ await this.setScheduledSignal(null);
5682
+ return await RETURN_IDLE_FN(this, currentPrice);
5683
+ }
5684
+ // Check risk before activation
5685
+ if (await functoolsKit.not(CALL_RISK_CHECK_SIGNAL_FN(this, this.params.execution.context.symbol, activatedSignal, currentPrice, currentTime, this.params.execution.context.backtest))) {
5686
+ this.params.logger.info("ClientStrategy tick: activated signal rejected by risk", {
5687
+ symbol: this.params.execution.context.symbol,
5688
+ signalId: activatedSignal.id,
5689
+ });
5690
+ return await RETURN_IDLE_FN(this, currentPrice);
5691
+ }
5692
+ // КРИТИЧЕСКИ ВАЖНО: обновляем pendingAt при активации
5693
+ const pendingSignal = {
5694
+ ...activatedSignal,
5695
+ pendingAt: currentTime,
5696
+ _isScheduled: false,
5697
+ };
5698
+ await this.setPendingSignal(pendingSignal);
5699
+ await CALL_RISK_ADD_SIGNAL_FN(this, this.params.execution.context.symbol, pendingSignal, currentTime, this.params.execution.context.backtest);
5700
+ // Emit commit AFTER successful risk check
5701
+ const publicSignalForCommit = TO_PUBLIC_SIGNAL(pendingSignal);
5702
+ await CALL_COMMIT_FN(this, {
5703
+ action: "activate-scheduled",
5704
+ symbol: this.params.execution.context.symbol,
5705
+ strategyName: this.params.strategyName,
5706
+ exchangeName: this.params.exchangeName,
5707
+ frameName: this.params.frameName,
5708
+ signalId: activatedSignal.id,
5709
+ backtest: this.params.execution.context.backtest,
5710
+ activateId: activatedSignal.activateId,
5711
+ timestamp: currentTime,
5712
+ currentPrice,
5713
+ position: publicSignalForCommit.position,
5714
+ priceOpen: publicSignalForCommit.priceOpen,
5715
+ priceTakeProfit: publicSignalForCommit.priceTakeProfit,
5716
+ priceStopLoss: publicSignalForCommit.priceStopLoss,
5717
+ originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5718
+ originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
5719
+ scheduledAt: publicSignalForCommit.scheduledAt,
5720
+ pendingAt: publicSignalForCommit.pendingAt,
5721
+ });
5722
+ // Call onOpen callback
5723
+ await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
5724
+ const result = {
5725
+ action: "opened",
5726
+ signal: TO_PUBLIC_SIGNAL(pendingSignal),
5727
+ strategyName: this.params.method.context.strategyName,
5728
+ exchangeName: this.params.method.context.exchangeName,
5729
+ frameName: this.params.method.context.frameName,
5730
+ symbol: this.params.execution.context.symbol,
5731
+ currentPrice,
5732
+ backtest: this.params.execution.context.backtest,
5733
+ createdAt: currentTime,
5734
+ };
5735
+ await CALL_TICK_CALLBACKS_FN(this, this.params.execution.context.symbol, result, currentTime, this.params.execution.context.backtest);
5736
+ return result;
5737
+ }
5568
5738
  // Monitor scheduled signal
5569
5739
  if (this._scheduledSignal && !this._pendingSignal) {
5570
5740
  const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
@@ -5588,7 +5758,12 @@ class ClientStrategy {
5588
5758
  return await RETURN_SCHEDULED_SIGNAL_ACTIVE_FN(this, this._scheduledSignal, currentPrice);
5589
5759
  }
5590
5760
  // Generate new signal if none exists
5761
+ // NOTE: _isStopped blocks NEW signal generation but allows existing positions to continue
5591
5762
  if (!this._pendingSignal && !this._scheduledSignal) {
5763
+ if (this._isStopped) {
5764
+ const currentPrice = await this.params.exchange.getAveragePrice(this.params.execution.context.symbol);
5765
+ return await RETURN_IDLE_FN(this, currentPrice);
5766
+ }
5592
5767
  const signal = await GET_SIGNAL_FN(this);
5593
5768
  if (signal) {
5594
5769
  if (signal._isScheduled === true) {
@@ -5662,6 +5837,18 @@ class ClientStrategy {
5662
5837
  const cancelledSignal = this._cancelledSignal;
5663
5838
  this._cancelledSignal = null; // Clear after using
5664
5839
  const closeTimestamp = this.params.execution.context.when.getTime();
5840
+ // Emit commit with correct timestamp from backtest context
5841
+ await CALL_COMMIT_FN(this, {
5842
+ action: "cancel-scheduled",
5843
+ symbol: this.params.execution.context.symbol,
5844
+ strategyName: this.params.strategyName,
5845
+ exchangeName: this.params.exchangeName,
5846
+ frameName: this.params.frameName,
5847
+ signalId: cancelledSignal.id,
5848
+ backtest: true,
5849
+ cancelId: cancelledSignal.cancelId,
5850
+ timestamp: closeTimestamp,
5851
+ });
5665
5852
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
5666
5853
  const cancelledResult = {
5667
5854
  action: "cancelled",
@@ -5687,6 +5874,18 @@ class ClientStrategy {
5687
5874
  const closedSignal = this._closedSignal;
5688
5875
  this._closedSignal = null; // Clear after using
5689
5876
  const closeTimestamp = this.params.execution.context.when.getTime();
5877
+ // Emit commit with correct timestamp from backtest context
5878
+ await CALL_COMMIT_FN(this, {
5879
+ action: "close-pending",
5880
+ symbol: this.params.execution.context.symbol,
5881
+ strategyName: this.params.strategyName,
5882
+ exchangeName: this.params.exchangeName,
5883
+ frameName: this.params.frameName,
5884
+ signalId: closedSignal.id,
5885
+ backtest: true,
5886
+ closeId: closedSignal.closeId,
5887
+ timestamp: closeTimestamp,
5888
+ });
5690
5889
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
5691
5890
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
5692
5891
  await CALL_PARTIAL_CLEAR_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
@@ -5847,8 +6046,18 @@ class ClientStrategy {
5847
6046
  symbol,
5848
6047
  hasPendingSignal: this._pendingSignal !== null,
5849
6048
  hasScheduledSignal: this._scheduledSignal !== null,
6049
+ hasActivatedSignal: this._activatedSignal !== null,
6050
+ hasCancelledSignal: this._cancelledSignal !== null,
6051
+ hasClosedSignal: this._closedSignal !== null,
5850
6052
  });
5851
6053
  this._isStopped = true;
6054
+ // Clear pending flags to start from clean state
6055
+ // NOTE: _isStopped blocks NEW position opening, but allows:
6056
+ // - cancelScheduled() / closePending() for graceful shutdown
6057
+ // - Monitoring existing _pendingSignal until TP/SL/timeout
6058
+ this._activatedSignal = null;
6059
+ this._cancelledSignal = null;
6060
+ this._closedSignal = null;
5852
6061
  // Clear scheduled signal if exists
5853
6062
  if (!this._scheduledSignal) {
5854
6063
  return;
@@ -5886,8 +6095,9 @@ class ClientStrategy {
5886
6095
  hasScheduledSignal: this._scheduledSignal !== null,
5887
6096
  cancelId,
5888
6097
  });
5889
- // Save cancelled signal for next tick to emit cancelled event
5890
- const hadScheduledSignal = this._scheduledSignal !== null;
6098
+ // NOTE: No _isStopped check - cancellation must work for graceful shutdown
6099
+ // (cancelling scheduled signal is not opening new position)
6100
+ // Save cancelled signal for next tick/backtest to emit cancelled event with correct timestamp
5891
6101
  if (this._scheduledSignal) {
5892
6102
  this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
5893
6103
  cancelId,
@@ -5895,37 +6105,60 @@ class ClientStrategy {
5895
6105
  this._scheduledSignal = null;
5896
6106
  }
5897
6107
  if (backtest) {
5898
- // Emit commit event only if signal was actually cancelled
5899
- if (hadScheduledSignal) {
5900
- await CALL_COMMIT_FN(this, {
5901
- action: "cancel-scheduled",
5902
- symbol,
5903
- strategyName: this.params.strategyName,
5904
- exchangeName: this.params.exchangeName,
5905
- frameName: this.params.frameName,
5906
- signalId: this._cancelledSignal.id,
5907
- backtest,
5908
- cancelId,
5909
- timestamp: this.params.execution.context.when.getTime(),
5910
- });
5911
- }
6108
+ // Commit will be emitted in backtest() with correct candle timestamp
5912
6109
  return;
5913
6110
  }
5914
6111
  await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.method.context.strategyName, this.params.method.context.exchangeName);
5915
- // Emit commit event only if signal was actually cancelled
5916
- if (hadScheduledSignal) {
5917
- await CALL_COMMIT_FN(this, {
5918
- action: "cancel-scheduled",
6112
+ // Commit will be emitted in tick() with correct currentTime
6113
+ }
6114
+ /**
6115
+ * Activates the scheduled signal without waiting for price to reach priceOpen.
6116
+ *
6117
+ * Forces immediate activation of the scheduled signal at the current price.
6118
+ * Does NOT affect active pending signals or strategy operation.
6119
+ * Does NOT set stop flag - strategy can continue generating new signals.
6120
+ *
6121
+ * Use case: User-initiated early activation of a scheduled entry.
6122
+ *
6123
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
6124
+ * @param backtest - Whether running in backtest mode
6125
+ * @param activateId - Optional identifier for this activation operation
6126
+ * @returns Promise that resolves when scheduled signal is activated
6127
+ *
6128
+ * @example
6129
+ * ```typescript
6130
+ * // Activate scheduled signal without waiting for priceOpen
6131
+ * await strategy.activateScheduled("BTCUSDT", false, "user-activate-123");
6132
+ * // Scheduled signal becomes pending signal immediately
6133
+ * ```
6134
+ */
6135
+ async activateScheduled(symbol, backtest, activateId) {
6136
+ this.params.logger.debug("ClientStrategy activateScheduled", {
6137
+ symbol,
6138
+ hasScheduledSignal: this._scheduledSignal !== null,
6139
+ activateId,
6140
+ });
6141
+ // Block activation if strategy stopped - activation = opening NEW position
6142
+ // (unlike cancelScheduled/closePending which handle existing signals for graceful shutdown)
6143
+ if (this._isStopped) {
6144
+ this.params.logger.debug("ClientStrategy activateScheduled: strategy stopped, skipping", {
5919
6145
  symbol,
5920
- strategyName: this.params.strategyName,
5921
- exchangeName: this.params.exchangeName,
5922
- frameName: this.params.frameName,
5923
- signalId: this._cancelledSignal.id,
5924
- backtest,
5925
- cancelId,
5926
- timestamp: this.params.execution.context.when.getTime(),
5927
6146
  });
6147
+ return;
6148
+ }
6149
+ // Save activated signal for next tick to emit opened event
6150
+ if (this._scheduledSignal) {
6151
+ this._activatedSignal = Object.assign({}, this._scheduledSignal, {
6152
+ activateId,
6153
+ });
6154
+ this._scheduledSignal = null;
6155
+ }
6156
+ if (backtest) {
6157
+ // Commit will be emitted AFTER successful risk check in PROCESS_SCHEDULED_SIGNAL_CANDLES_FN
6158
+ return;
5928
6159
  }
6160
+ await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, symbol, this.params.method.context.strategyName, this.params.method.context.exchangeName);
6161
+ // Commit will be emitted AFTER successful risk check in tick()
5929
6162
  }
5930
6163
  /**
5931
6164
  * Closes the pending signal without stopping the strategy.
@@ -5954,8 +6187,8 @@ class ClientStrategy {
5954
6187
  hasPendingSignal: this._pendingSignal !== null,
5955
6188
  closeId,
5956
6189
  });
5957
- // Save closed signal for next tick to emit closed event
5958
- const hadPendingSignal = this._pendingSignal !== null;
6190
+ // NOTE: No _isStopped check - closing position must work for graceful shutdown
6191
+ // Save closed signal for next tick/backtest to emit closed event with correct timestamp
5959
6192
  if (this._pendingSignal) {
5960
6193
  this._closedSignal = Object.assign({}, this._pendingSignal, {
5961
6194
  closeId,
@@ -5963,37 +6196,11 @@ class ClientStrategy {
5963
6196
  this._pendingSignal = null;
5964
6197
  }
5965
6198
  if (backtest) {
5966
- // Emit commit event only if signal was actually closed
5967
- if (hadPendingSignal) {
5968
- await CALL_COMMIT_FN(this, {
5969
- action: "close-pending",
5970
- symbol,
5971
- strategyName: this.params.strategyName,
5972
- exchangeName: this.params.exchangeName,
5973
- frameName: this.params.frameName,
5974
- signalId: this._closedSignal.id,
5975
- backtest,
5976
- closeId,
5977
- timestamp: this.params.execution.context.when.getTime(),
5978
- });
5979
- }
6199
+ // Commit will be emitted in backtest() with correct candle timestamp
5980
6200
  return;
5981
6201
  }
5982
6202
  await PersistSignalAdapter.writeSignalData(this._pendingSignal, symbol, this.params.strategyName, this.params.exchangeName);
5983
- // Emit commit event only if signal was actually closed
5984
- if (hadPendingSignal) {
5985
- await CALL_COMMIT_FN(this, {
5986
- action: "close-pending",
5987
- symbol,
5988
- strategyName: this.params.strategyName,
5989
- exchangeName: this.params.exchangeName,
5990
- frameName: this.params.frameName,
5991
- signalId: this._closedSignal.id,
5992
- backtest,
5993
- closeId,
5994
- timestamp: this.params.execution.context.when.getTime(),
5995
- });
5996
- }
6203
+ // Commit will be emitted in tick() with correct currentTime
5997
6204
  }
5998
6205
  /**
5999
6206
  * Executes partial close at profit level (moving toward TP).
@@ -7832,6 +8039,39 @@ class StrategyConnectionService {
7832
8039
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
7833
8040
  return await strategy.breakeven(symbol, currentPrice, backtest);
7834
8041
  };
8042
+ /**
8043
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
8044
+ *
8045
+ * Delegates to ClientStrategy.activateScheduled() which sets _activatedSignal flag.
8046
+ * The actual activation happens on next tick() when strategy detects the flag.
8047
+ *
8048
+ * @param backtest - Whether running in backtest mode
8049
+ * @param symbol - Trading pair symbol
8050
+ * @param context - Execution context with strategyName, exchangeName, frameName
8051
+ * @param activateId - Optional identifier for the activation reason
8052
+ * @returns Promise that resolves when activation flag is set
8053
+ *
8054
+ * @example
8055
+ * ```typescript
8056
+ * // Activate scheduled signal early
8057
+ * await strategyConnectionService.activateScheduled(
8058
+ * false,
8059
+ * "BTCUSDT",
8060
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
8061
+ * "manual-activation"
8062
+ * );
8063
+ * ```
8064
+ */
8065
+ this.activateScheduled = async (backtest, symbol, context, activateId) => {
8066
+ this.loggerService.log("strategyConnectionService activateScheduled", {
8067
+ symbol,
8068
+ context,
8069
+ backtest,
8070
+ activateId,
8071
+ });
8072
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
8073
+ return await strategy.activateScheduled(symbol, backtest, activateId);
8074
+ };
7835
8075
  }
7836
8076
  }
7837
8077
 
@@ -11144,6 +11384,39 @@ class StrategyCoreService {
11144
11384
  await this.validate(context);
11145
11385
  return await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
11146
11386
  };
11387
+ /**
11388
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
11389
+ *
11390
+ * Validates strategy existence and delegates to connection service
11391
+ * to set the activation flag. The actual activation happens on next tick().
11392
+ *
11393
+ * @param backtest - Whether running in backtest mode
11394
+ * @param symbol - Trading pair symbol
11395
+ * @param context - Execution context with strategyName, exchangeName, frameName
11396
+ * @param activateId - Optional identifier for the activation reason
11397
+ * @returns Promise that resolves when activation flag is set
11398
+ *
11399
+ * @example
11400
+ * ```typescript
11401
+ * // Activate scheduled signal early
11402
+ * await strategyCoreService.activateScheduled(
11403
+ * false,
11404
+ * "BTCUSDT",
11405
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
11406
+ * "manual-activation"
11407
+ * );
11408
+ * ```
11409
+ */
11410
+ this.activateScheduled = async (backtest, symbol, context, activateId) => {
11411
+ this.loggerService.log("strategyCoreService activateScheduled", {
11412
+ symbol,
11413
+ context,
11414
+ backtest,
11415
+ activateId,
11416
+ });
11417
+ await this.validate(context);
11418
+ return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
11419
+ };
11147
11420
  }
11148
11421
  }
11149
11422
 
@@ -12423,6 +12696,8 @@ class WalkerSchemaService {
12423
12696
  }
12424
12697
  }
12425
12698
 
12699
+ const ACTIVE_CANDLE_INCLUDED = 1;
12700
+ const SCHEDULE_ACTIVATION_CANDLE_SKIP = 1;
12426
12701
  /**
12427
12702
  * Private service for backtest orchestration using async generators.
12428
12703
  *
@@ -12546,9 +12821,9 @@ class BacktestLogicPrivateService {
12546
12821
  // - CC_SCHEDULE_AWAIT_MINUTES для ожидания активации
12547
12822
  // - minuteEstimatedTime для работы сигнала ПОСЛЕ активации
12548
12823
  // - +1 потому что when включается как первая свеча
12549
- const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - 1;
12824
+ const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - ACTIVE_CANDLE_INCLUDED;
12550
12825
  const bufferStartTime = new Date(when.getTime() - bufferMinutes * 60 * 1000);
12551
- const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
12826
+ const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + SCHEDULE_ACTIVATION_CANDLE_SKIP;
12552
12827
  let candles;
12553
12828
  try {
12554
12829
  candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
@@ -12686,7 +12961,7 @@ class BacktestLogicPrivateService {
12686
12961
  // КРИТИЧНО: Получаем свечи включая буфер для VWAP
12687
12962
  // Сдвигаем начало назад на CC_AVG_PRICE_CANDLES_COUNT-1 минут для буфера VWAP
12688
12963
  // Запрашиваем minuteEstimatedTime + буфер свечей одним запросом
12689
- const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - 1;
12964
+ const bufferMinutes = GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT - ACTIVE_CANDLE_INCLUDED;
12690
12965
  const bufferStartTime = new Date(when.getTime() - bufferMinutes * 60 * 1000);
12691
12966
  const totalCandles = signal.minuteEstimatedTime + GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT;
12692
12967
  let candles;
@@ -24376,6 +24651,67 @@ class StrategyReportService {
24376
24651
  walkerName: "",
24377
24652
  });
24378
24653
  };
24654
+ /**
24655
+ * Logs an activate-scheduled event when a scheduled signal is activated early.
24656
+ *
24657
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
24658
+ * @param currentPrice - Current market price at time of activation
24659
+ * @param isBacktest - Whether this is a backtest or live trading event
24660
+ * @param context - Strategy context with strategyName, exchangeName, frameName
24661
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
24662
+ * @param position - Trade direction: "long" or "short"
24663
+ * @param priceOpen - Entry price for the position
24664
+ * @param priceTakeProfit - Effective take profit price
24665
+ * @param priceStopLoss - Effective stop loss price
24666
+ * @param originalPriceTakeProfit - Original take profit before trailing
24667
+ * @param originalPriceStopLoss - Original stop loss before trailing
24668
+ * @param scheduledAt - Signal creation timestamp in milliseconds
24669
+ * @param pendingAt - Pending timestamp in milliseconds
24670
+ * @param activateId - Optional identifier for the activation reason
24671
+ */
24672
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
24673
+ this.loggerService.log("strategyReportService activateScheduled", {
24674
+ symbol,
24675
+ currentPrice,
24676
+ isBacktest,
24677
+ activateId,
24678
+ });
24679
+ if (!this.subscribe.hasValue()) {
24680
+ return;
24681
+ }
24682
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
24683
+ exchangeName: context.exchangeName,
24684
+ strategyName: context.strategyName,
24685
+ frameName: context.frameName,
24686
+ });
24687
+ if (!scheduledRow) {
24688
+ return;
24689
+ }
24690
+ const createdAt = new Date(timestamp).toISOString();
24691
+ await Report.writeData("strategy", {
24692
+ action: "activate-scheduled",
24693
+ activateId,
24694
+ currentPrice,
24695
+ symbol,
24696
+ timestamp,
24697
+ createdAt,
24698
+ position,
24699
+ priceOpen,
24700
+ priceTakeProfit,
24701
+ priceStopLoss,
24702
+ originalPriceTakeProfit,
24703
+ originalPriceStopLoss,
24704
+ scheduledAt,
24705
+ pendingAt,
24706
+ }, {
24707
+ signalId: scheduledRow.id,
24708
+ exchangeName: context.exchangeName,
24709
+ frameName: context.frameName,
24710
+ strategyName: context.strategyName,
24711
+ symbol,
24712
+ walkerName: "",
24713
+ });
24714
+ };
24379
24715
  /**
24380
24716
  * Initializes the service for event logging.
24381
24717
  *
@@ -24435,7 +24771,14 @@ class StrategyReportService {
24435
24771
  frameName: event.frameName,
24436
24772
  strategyName: event.strategyName,
24437
24773
  }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
24438
- const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
24774
+ const unActivateScheduled = strategyCommitSubject
24775
+ .filter(({ action }) => action === "activate-scheduled")
24776
+ .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
24777
+ exchangeName: event.exchangeName,
24778
+ frameName: event.frameName,
24779
+ strategyName: event.strategyName,
24780
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
24781
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
24439
24782
  return () => {
24440
24783
  disposeFn();
24441
24784
  this.subscribe.clear();
@@ -24566,6 +24909,7 @@ class ReportStorage {
24566
24909
  trailingStopCount: 0,
24567
24910
  trailingTakeCount: 0,
24568
24911
  breakevenCount: 0,
24912
+ activateScheduledCount: 0,
24569
24913
  };
24570
24914
  }
24571
24915
  return {
@@ -24578,6 +24922,7 @@ class ReportStorage {
24578
24922
  trailingStopCount: this._eventList.filter(e => e.action === "trailing-stop").length,
24579
24923
  trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
24580
24924
  breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
24925
+ activateScheduledCount: this._eventList.filter(e => e.action === "activate-scheduled").length,
24581
24926
  };
24582
24927
  }
24583
24928
  /**
@@ -24626,6 +24971,7 @@ class ReportStorage {
24626
24971
  `- Trailing stop: ${stats.trailingStopCount}`,
24627
24972
  `- Trailing take: ${stats.trailingTakeCount}`,
24628
24973
  `- Breakeven: ${stats.breakevenCount}`,
24974
+ `- Activate scheduled: ${stats.activateScheduledCount}`,
24629
24975
  ].join("\n");
24630
24976
  }
24631
24977
  /**
@@ -25092,6 +25438,66 @@ class StrategyMarkdownService {
25092
25438
  pendingAt,
25093
25439
  });
25094
25440
  };
25441
+ /**
25442
+ * Records an activate-scheduled event when a scheduled signal is activated early.
25443
+ *
25444
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
25445
+ * @param currentPrice - Current market price at time of activation
25446
+ * @param isBacktest - Whether this is a backtest or live trading event
25447
+ * @param context - Strategy context with strategyName, exchangeName, frameName
25448
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
25449
+ * @param position - Trade direction: "long" or "short"
25450
+ * @param priceOpen - Entry price for the position
25451
+ * @param priceTakeProfit - Effective take profit price
25452
+ * @param priceStopLoss - Effective stop loss price
25453
+ * @param originalPriceTakeProfit - Original take profit before trailing
25454
+ * @param originalPriceStopLoss - Original stop loss before trailing
25455
+ * @param scheduledAt - Signal creation timestamp in milliseconds
25456
+ * @param pendingAt - Pending timestamp in milliseconds
25457
+ * @param activateId - Optional identifier for the activation reason
25458
+ */
25459
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
25460
+ this.loggerService.log("strategyMarkdownService activateScheduled", {
25461
+ symbol,
25462
+ currentPrice,
25463
+ isBacktest,
25464
+ activateId,
25465
+ });
25466
+ if (!this.subscribe.hasValue()) {
25467
+ return;
25468
+ }
25469
+ const scheduledRow = await this.strategyCoreService.getScheduledSignal(isBacktest, symbol, {
25470
+ exchangeName: context.exchangeName,
25471
+ strategyName: context.strategyName,
25472
+ frameName: context.frameName,
25473
+ });
25474
+ if (!scheduledRow) {
25475
+ return;
25476
+ }
25477
+ const createdAt = new Date(timestamp).toISOString();
25478
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
25479
+ storage.addEvent({
25480
+ timestamp,
25481
+ symbol,
25482
+ strategyName: context.strategyName,
25483
+ exchangeName: context.exchangeName,
25484
+ frameName: context.frameName,
25485
+ signalId: scheduledRow.id,
25486
+ action: "activate-scheduled",
25487
+ activateId,
25488
+ currentPrice,
25489
+ createdAt,
25490
+ backtest: isBacktest,
25491
+ position,
25492
+ priceOpen,
25493
+ priceTakeProfit,
25494
+ priceStopLoss,
25495
+ originalPriceTakeProfit,
25496
+ originalPriceStopLoss,
25497
+ scheduledAt,
25498
+ pendingAt,
25499
+ });
25500
+ };
25095
25501
  /**
25096
25502
  * Retrieves aggregated statistics from accumulated strategy events.
25097
25503
  *
@@ -25265,7 +25671,14 @@ class StrategyMarkdownService {
25265
25671
  frameName: event.frameName,
25266
25672
  strategyName: event.strategyName,
25267
25673
  }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25268
- const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven());
25674
+ const unActivateScheduled = strategyCommitSubject
25675
+ .filter(({ action }) => action === "activate-scheduled")
25676
+ .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
25677
+ exchangeName: event.exchangeName,
25678
+ frameName: event.frameName,
25679
+ strategyName: event.strategyName,
25680
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
25681
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
25269
25682
  return () => {
25270
25683
  disposeFn();
25271
25684
  this.subscribe.clear();
@@ -26191,6 +26604,7 @@ const PARTIAL_LOSS_METHOD_NAME = "strategy.commitPartialLoss";
26191
26604
  const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
26192
26605
  const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
26193
26606
  const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
26607
+ const ACTIVATE_SCHEDULED_METHOD_NAME = "strategy.commitActivateScheduled";
26194
26608
  /**
26195
26609
  * Cancels the scheduled signal without stopping the strategy.
26196
26610
  *
@@ -26508,6 +26922,41 @@ async function commitBreakeven(symbol) {
26508
26922
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
26509
26923
  return await bt.strategyCoreService.breakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
26510
26924
  }
26925
+ /**
26926
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
26927
+ *
26928
+ * Sets the activation flag on the scheduled signal. The actual activation
26929
+ * happens on the next tick() when strategy detects the flag.
26930
+ *
26931
+ * Automatically detects backtest/live mode from execution context.
26932
+ *
26933
+ * @param symbol - Trading pair symbol
26934
+ * @param activateId - Optional activation ID for tracking user-initiated activations
26935
+ * @returns Promise that resolves when activation flag is set
26936
+ *
26937
+ * @example
26938
+ * ```typescript
26939
+ * import { commitActivateScheduled } from "backtest-kit";
26940
+ *
26941
+ * // Activate scheduled signal early with custom ID
26942
+ * await commitActivateScheduled("BTCUSDT", "manual-activate-001");
26943
+ * ```
26944
+ */
26945
+ async function commitActivateScheduled(symbol, activateId) {
26946
+ bt.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
26947
+ symbol,
26948
+ activateId,
26949
+ });
26950
+ if (!ExecutionContextService.hasContext()) {
26951
+ throw new Error("commitActivateScheduled requires an execution context");
26952
+ }
26953
+ if (!MethodContextService.hasContext()) {
26954
+ throw new Error("commitActivateScheduled requires a method context");
26955
+ }
26956
+ const { backtest: isBacktest } = bt.executionContextService.context;
26957
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
26958
+ await bt.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
26959
+ }
26511
26960
 
26512
26961
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
26513
26962
  /**
@@ -28678,6 +29127,7 @@ const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.commitPartialProfit";
28678
29127
  const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
28679
29128
  const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
28680
29129
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
29130
+ const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
28681
29131
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
28682
29132
  /**
28683
29133
  * Internal task function that runs backtest and handles completion.
@@ -29534,6 +29984,46 @@ class BacktestUtils {
29534
29984
  }
29535
29985
  return await bt.strategyCoreService.breakeven(true, symbol, currentPrice, context);
29536
29986
  };
29987
+ /**
29988
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
29989
+ *
29990
+ * Sets the activation flag on the scheduled signal. The actual activation
29991
+ * happens on the next tick() when strategy detects the flag.
29992
+ *
29993
+ * @param symbol - Trading pair symbol
29994
+ * @param context - Execution context with strategyName, exchangeName, and frameName
29995
+ * @param activateId - Optional activation ID for tracking user-initiated activations
29996
+ * @returns Promise that resolves when activation flag is set
29997
+ *
29998
+ * @example
29999
+ * ```typescript
30000
+ * // Activate scheduled signal early with custom ID
30001
+ * await Backtest.commitActivateScheduled("BTCUSDT", {
30002
+ * strategyName: "my-strategy",
30003
+ * exchangeName: "binance",
30004
+ * frameName: "1h"
30005
+ * }, "manual-activate-001");
30006
+ * ```
30007
+ */
30008
+ this.commitActivateScheduled = async (symbol, context, activateId) => {
30009
+ bt.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
30010
+ symbol,
30011
+ context,
30012
+ activateId,
30013
+ });
30014
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
30015
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
30016
+ {
30017
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
30018
+ riskName &&
30019
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
30020
+ riskList &&
30021
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
30022
+ actions &&
30023
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
30024
+ }
30025
+ await bt.strategyCoreService.activateScheduled(true, symbol, context, activateId);
30026
+ };
29537
30027
  /**
29538
30028
  * Gets statistical data from all closed signals for a symbol-strategy pair.
29539
30029
  *
@@ -29709,6 +30199,7 @@ const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.commitPartialProfit";
29709
30199
  const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
29710
30200
  const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
29711
30201
  const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
30202
+ const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
29712
30203
  /**
29713
30204
  * Internal task function that runs live trading and handles completion.
29714
30205
  * Consumes live trading results and updates instance state flags.
@@ -30533,6 +31024,46 @@ class LiveUtils {
30533
31024
  frameName: "",
30534
31025
  });
30535
31026
  };
31027
+ /**
31028
+ * Activates a scheduled signal early without waiting for price to reach priceOpen.
31029
+ *
31030
+ * Sets the activation flag on the scheduled signal. The actual activation
31031
+ * happens on the next tick() when strategy detects the flag.
31032
+ *
31033
+ * @param symbol - Trading pair symbol
31034
+ * @param context - Execution context with strategyName and exchangeName
31035
+ * @param activateId - Optional activation ID for tracking user-initiated activations
31036
+ * @returns Promise that resolves when activation flag is set
31037
+ *
31038
+ * @example
31039
+ * ```typescript
31040
+ * // Activate scheduled signal early with custom ID
31041
+ * await Live.commitActivateScheduled("BTCUSDT", {
31042
+ * strategyName: "my-strategy",
31043
+ * exchangeName: "binance"
31044
+ * }, "manual-activate-001");
31045
+ * ```
31046
+ */
31047
+ this.commitActivateScheduled = async (symbol, context, activateId) => {
31048
+ bt.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
31049
+ symbol,
31050
+ context,
31051
+ activateId,
31052
+ });
31053
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
31054
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
31055
+ {
31056
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
31057
+ riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
31058
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
31059
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
31060
+ }
31061
+ await bt.strategyCoreService.activateScheduled(false, symbol, {
31062
+ strategyName: context.strategyName,
31063
+ exchangeName: context.exchangeName,
31064
+ frameName: "",
31065
+ }, activateId);
31066
+ };
30536
31067
  /**
30537
31068
  * Gets statistical data from all live trading events for a symbol-strategy pair.
30538
31069
  *
@@ -34468,6 +34999,29 @@ class NotificationInstance {
34468
34999
  createdAt: data.timestamp,
34469
35000
  });
34470
35001
  }
35002
+ else if (data.action === "activate-scheduled") {
35003
+ this._addNotification({
35004
+ type: "activate_scheduled.commit",
35005
+ id: CREATE_KEY_FN(),
35006
+ timestamp: data.timestamp,
35007
+ backtest: data.backtest,
35008
+ symbol: data.symbol,
35009
+ strategyName: data.strategyName,
35010
+ exchangeName: data.exchangeName,
35011
+ signalId: data.signalId,
35012
+ activateId: data.activateId,
35013
+ currentPrice: data.currentPrice,
35014
+ position: data.position,
35015
+ priceOpen: data.priceOpen,
35016
+ priceTakeProfit: data.priceTakeProfit,
35017
+ priceStopLoss: data.priceStopLoss,
35018
+ originalPriceTakeProfit: data.originalPriceTakeProfit,
35019
+ originalPriceStopLoss: data.originalPriceStopLoss,
35020
+ scheduledAt: data.scheduledAt,
35021
+ pendingAt: data.pendingAt,
35022
+ createdAt: data.timestamp,
35023
+ });
35024
+ }
34471
35025
  };
34472
35026
  /**
34473
35027
  * Processes risk rejection events.
@@ -35313,6 +35867,7 @@ exports.addRiskSchema = addRiskSchema;
35313
35867
  exports.addSizingSchema = addSizingSchema;
35314
35868
  exports.addStrategySchema = addStrategySchema;
35315
35869
  exports.addWalkerSchema = addWalkerSchema;
35870
+ exports.commitActivateScheduled = commitActivateScheduled;
35316
35871
  exports.commitBreakeven = commitBreakeven;
35317
35872
  exports.commitCancelScheduled = commitCancelScheduled;
35318
35873
  exports.commitClosePending = commitClosePending;