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