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/README.md +28 -9
- package/build/index.cjs +622 -67
- package/build/index.mjs +622 -68
- package/package.json +1 -2
- package/types.d.ts +349 -5
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
|
-
//
|
|
5890
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
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
|
-
//
|
|
5958
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 -
|
|
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 +
|
|
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 -
|
|
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
|
|
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
|
|
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;
|