backtest-kit 6.10.0 → 6.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +1356 -191
- package/build/index.mjs +1354 -192
- package/package.json +2 -2
- package/types.d.ts +953 -118
package/build/index.mjs
CHANGED
|
@@ -4127,53 +4127,6 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
4127
4127
|
};
|
|
4128
4128
|
};
|
|
4129
4129
|
|
|
4130
|
-
/**
|
|
4131
|
-
* Converts markdown content to plain text with minimal formatting
|
|
4132
|
-
* @param content - Markdown string to convert
|
|
4133
|
-
* @returns Plain text representation
|
|
4134
|
-
*/
|
|
4135
|
-
const toPlainString = (content) => {
|
|
4136
|
-
if (!content) {
|
|
4137
|
-
return "";
|
|
4138
|
-
}
|
|
4139
|
-
let text = content;
|
|
4140
|
-
// Remove code blocks
|
|
4141
|
-
text = text.replace(/```[\s\S]*?```/g, "");
|
|
4142
|
-
text = text.replace(/`([^`]+)`/g, "$1");
|
|
4143
|
-
// Remove images
|
|
4144
|
-
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
4145
|
-
// Convert links to text only (keep link text, remove URL)
|
|
4146
|
-
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
4147
|
-
// Remove headers (convert to plain text)
|
|
4148
|
-
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
4149
|
-
// Remove bold and italic markers
|
|
4150
|
-
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
4151
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
4152
|
-
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
4153
|
-
text = text.replace(/___(.+?)___/g, "$1");
|
|
4154
|
-
text = text.replace(/__(.+?)__/g, "$1");
|
|
4155
|
-
text = text.replace(/_(.+?)_/g, "$1");
|
|
4156
|
-
// Remove strikethrough
|
|
4157
|
-
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
4158
|
-
// Convert lists to plain text with bullets
|
|
4159
|
-
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
4160
|
-
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
4161
|
-
// Remove blockquotes
|
|
4162
|
-
text = text.replace(/^\s*>\s+/gm, "");
|
|
4163
|
-
// Remove horizontal rules
|
|
4164
|
-
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
4165
|
-
// Remove HTML tags
|
|
4166
|
-
text = text.replace(/<[^>]+>/g, "");
|
|
4167
|
-
// Remove excessive whitespace and normalize line breaks
|
|
4168
|
-
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
4169
|
-
text = text.replace(/[ \t]+/g, " ");
|
|
4170
|
-
// Remove all newline characters
|
|
4171
|
-
text = text.replace(/\n/g, " ");
|
|
4172
|
-
// Remove excessive spaces after newline removal
|
|
4173
|
-
text = text.replace(/\s+/g, " ");
|
|
4174
|
-
return text.trim();
|
|
4175
|
-
};
|
|
4176
|
-
|
|
4177
4130
|
/**
|
|
4178
4131
|
* Returns the total closed state of a position using costBasisAtClose snapshots.
|
|
4179
4132
|
*
|
|
@@ -4808,6 +4761,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4808
4761
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4809
4762
|
scheduledAt: publicSignal.scheduledAt,
|
|
4810
4763
|
pendingAt: publicSignal.pendingAt,
|
|
4764
|
+
note: publicSignal.note,
|
|
4811
4765
|
});
|
|
4812
4766
|
continue;
|
|
4813
4767
|
}
|
|
@@ -4835,6 +4789,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4835
4789
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4836
4790
|
scheduledAt: publicSignal.scheduledAt,
|
|
4837
4791
|
pendingAt: publicSignal.pendingAt,
|
|
4792
|
+
note: publicSignal.note,
|
|
4838
4793
|
});
|
|
4839
4794
|
continue;
|
|
4840
4795
|
}
|
|
@@ -4861,6 +4816,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4861
4816
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4862
4817
|
scheduledAt: publicSignal.scheduledAt,
|
|
4863
4818
|
pendingAt: publicSignal.pendingAt,
|
|
4819
|
+
note: publicSignal.note,
|
|
4864
4820
|
});
|
|
4865
4821
|
continue;
|
|
4866
4822
|
}
|
|
@@ -4888,6 +4844,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4888
4844
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4889
4845
|
scheduledAt: publicSignal.scheduledAt,
|
|
4890
4846
|
pendingAt: publicSignal.pendingAt,
|
|
4847
|
+
note: publicSignal.note,
|
|
4891
4848
|
});
|
|
4892
4849
|
continue;
|
|
4893
4850
|
}
|
|
@@ -4915,6 +4872,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4915
4872
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4916
4873
|
scheduledAt: publicSignal.scheduledAt,
|
|
4917
4874
|
pendingAt: publicSignal.pendingAt,
|
|
4875
|
+
note: publicSignal.note,
|
|
4918
4876
|
});
|
|
4919
4877
|
continue;
|
|
4920
4878
|
}
|
|
@@ -4944,6 +4902,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4944
4902
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4945
4903
|
scheduledAt: publicSignal.scheduledAt,
|
|
4946
4904
|
pendingAt: publicSignal.pendingAt,
|
|
4905
|
+
note: publicSignal.note,
|
|
4947
4906
|
});
|
|
4948
4907
|
continue;
|
|
4949
4908
|
}
|
|
@@ -5042,7 +5001,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5042
5001
|
const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
|
|
5043
5002
|
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
5044
5003
|
const signal = await Promise.race([
|
|
5045
|
-
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
|
|
5004
|
+
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
|
|
5046
5005
|
sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
|
|
5047
5006
|
]);
|
|
5048
5007
|
if (typeof signal === "symbol") {
|
|
@@ -5072,7 +5031,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5072
5031
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5073
5032
|
priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
|
|
5074
5033
|
position: signal.position,
|
|
5075
|
-
note:
|
|
5034
|
+
note: signal.note || "",
|
|
5076
5035
|
priceTakeProfit: signal.priceTakeProfit,
|
|
5077
5036
|
priceStopLoss: signal.priceStopLoss,
|
|
5078
5037
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
@@ -5098,7 +5057,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5098
5057
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5099
5058
|
priceOpen: signal.priceOpen,
|
|
5100
5059
|
position: signal.position,
|
|
5101
|
-
note:
|
|
5060
|
+
note: signal.note || "",
|
|
5102
5061
|
priceTakeProfit: signal.priceTakeProfit,
|
|
5103
5062
|
priceStopLoss: signal.priceStopLoss,
|
|
5104
5063
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
@@ -5123,7 +5082,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5123
5082
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5124
5083
|
priceOpen: currentPrice,
|
|
5125
5084
|
...structuredClone(signal),
|
|
5126
|
-
note:
|
|
5085
|
+
note: signal.note || "",
|
|
5127
5086
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
5128
5087
|
symbol: self.params.execution.context.symbol,
|
|
5129
5088
|
exchangeName: self.params.method.context.exchangeName,
|
|
@@ -5870,6 +5829,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
5870
5829
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
5871
5830
|
originalPriceOpen: scheduled.priceOpen,
|
|
5872
5831
|
pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
|
|
5832
|
+
note: scheduled.note,
|
|
5873
5833
|
});
|
|
5874
5834
|
return null;
|
|
5875
5835
|
}
|
|
@@ -6613,7 +6573,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
6613
6573
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
6614
6574
|
return result;
|
|
6615
6575
|
};
|
|
6616
|
-
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
|
|
6576
|
+
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId, cancelNote) => {
|
|
6617
6577
|
self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
|
|
6618
6578
|
symbol: self.params.execution.context.symbol,
|
|
6619
6579
|
signalId: scheduled.id,
|
|
@@ -6638,6 +6598,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
6638
6598
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6639
6599
|
originalPriceOpen: scheduled.priceOpen,
|
|
6640
6600
|
pnl: toProfitLossDto(scheduled, averagePrice),
|
|
6601
|
+
note: cancelNote ?? scheduled.note,
|
|
6641
6602
|
});
|
|
6642
6603
|
}
|
|
6643
6604
|
await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6716,6 +6677,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
6716
6677
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6717
6678
|
originalPriceOpen: scheduled.priceOpen,
|
|
6718
6679
|
pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
|
|
6680
|
+
note: scheduled.note,
|
|
6719
6681
|
});
|
|
6720
6682
|
return false;
|
|
6721
6683
|
}
|
|
@@ -6803,6 +6765,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
|
|
|
6803
6765
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
6804
6766
|
originalPriceOpen: closedSignal.priceOpen,
|
|
6805
6767
|
pnl: toProfitLossDto(closedSignal, averagePrice),
|
|
6768
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
6806
6769
|
});
|
|
6807
6770
|
await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
6808
6771
|
await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6849,7 +6812,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6849
6812
|
if (self._cancelledSignal) {
|
|
6850
6813
|
// Сигнал был отменен через cancel() в onSchedulePing
|
|
6851
6814
|
const cancelId = self._cancelledSignal.cancelId;
|
|
6852
|
-
const
|
|
6815
|
+
const cancelNote = self._cancelledSignal.cancelNote;
|
|
6816
|
+
const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId, cancelNote);
|
|
6853
6817
|
return { outcome: "cancelled", result };
|
|
6854
6818
|
}
|
|
6855
6819
|
// КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
|
|
@@ -6903,6 +6867,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6903
6867
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
6904
6868
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
6905
6869
|
pnl: toProfitLossDto(activatedSignal, averagePrice),
|
|
6870
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
6906
6871
|
});
|
|
6907
6872
|
return { outcome: "pending" };
|
|
6908
6873
|
}
|
|
@@ -6934,6 +6899,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6934
6899
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
6935
6900
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
6936
6901
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
6902
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
6937
6903
|
});
|
|
6938
6904
|
await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
|
|
6939
6905
|
await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
|
|
@@ -8054,6 +8020,44 @@ class ClientStrategy {
|
|
|
8054
8020
|
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8055
8021
|
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8056
8022
|
}
|
|
8023
|
+
/**
|
|
8024
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
8025
|
+
*
|
|
8026
|
+
* Measures the total swing from the stored `_peak.pnlPercentage` to the stored `_fall.pnlPercentage`.
|
|
8027
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
8028
|
+
*
|
|
8029
|
+
* Returns null if no pending signal exists.
|
|
8030
|
+
*
|
|
8031
|
+
* @param symbol - Trading pair symbol
|
|
8032
|
+
* @param currentPrice - Current market price
|
|
8033
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
8034
|
+
*/
|
|
8035
|
+
async getMaxDrawdownDistancePnlPercentage(symbol, currentPrice) {
|
|
8036
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlPercentage", { symbol, currentPrice });
|
|
8037
|
+
if (!this._pendingSignal) {
|
|
8038
|
+
return null;
|
|
8039
|
+
}
|
|
8040
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8041
|
+
}
|
|
8042
|
+
/**
|
|
8043
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
8044
|
+
*
|
|
8045
|
+
* Measures the total swing from the stored `_peak.pnlCost` to the stored `_fall.pnlCost`.
|
|
8046
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
8047
|
+
*
|
|
8048
|
+
* Returns null if no pending signal exists.
|
|
8049
|
+
*
|
|
8050
|
+
* @param symbol - Trading pair symbol
|
|
8051
|
+
* @param currentPrice - Current market price
|
|
8052
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
8053
|
+
*/
|
|
8054
|
+
async getMaxDrawdownDistancePnlCost(symbol, currentPrice) {
|
|
8055
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlCost", { symbol, currentPrice });
|
|
8056
|
+
if (!this._pendingSignal) {
|
|
8057
|
+
return null;
|
|
8058
|
+
}
|
|
8059
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8060
|
+
}
|
|
8057
8061
|
/**
|
|
8058
8062
|
* Performs a single tick of strategy execution.
|
|
8059
8063
|
*
|
|
@@ -8118,6 +8122,7 @@ class ClientStrategy {
|
|
|
8118
8122
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8119
8123
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8120
8124
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8125
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8121
8126
|
});
|
|
8122
8127
|
// Call onCancel callback
|
|
8123
8128
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8171,6 +8176,7 @@ class ClientStrategy {
|
|
|
8171
8176
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8172
8177
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8173
8178
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8179
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8174
8180
|
});
|
|
8175
8181
|
// Call onClose callback
|
|
8176
8182
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8252,6 +8258,7 @@ class ClientStrategy {
|
|
|
8252
8258
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
8253
8259
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
8254
8260
|
pnl: toProfitLossDto(activatedSignal, currentPrice),
|
|
8261
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
8255
8262
|
});
|
|
8256
8263
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
8257
8264
|
}
|
|
@@ -8282,6 +8289,7 @@ class ClientStrategy {
|
|
|
8282
8289
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
8283
8290
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
8284
8291
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
8292
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
8285
8293
|
});
|
|
8286
8294
|
// Call onOpen callback
|
|
8287
8295
|
await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8417,6 +8425,7 @@ class ClientStrategy {
|
|
|
8417
8425
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8418
8426
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8419
8427
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8428
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8420
8429
|
});
|
|
8421
8430
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8422
8431
|
const cancelledResult = {
|
|
@@ -8471,6 +8480,7 @@ class ClientStrategy {
|
|
|
8471
8480
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8472
8481
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8473
8482
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8483
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8474
8484
|
});
|
|
8475
8485
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8476
8486
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
@@ -8654,7 +8664,8 @@ class ClientStrategy {
|
|
|
8654
8664
|
* // Strategy continues, can generate new signals
|
|
8655
8665
|
* ```
|
|
8656
8666
|
*/
|
|
8657
|
-
async cancelScheduled(symbol, backtest,
|
|
8667
|
+
async cancelScheduled(symbol, backtest, payload) {
|
|
8668
|
+
const cancelId = payload.id;
|
|
8658
8669
|
this.params.logger.debug("ClientStrategy cancelScheduled", {
|
|
8659
8670
|
symbol,
|
|
8660
8671
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8666,6 +8677,7 @@ class ClientStrategy {
|
|
|
8666
8677
|
if (this._scheduledSignal) {
|
|
8667
8678
|
this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
|
|
8668
8679
|
cancelId,
|
|
8680
|
+
cancelNote: payload.note,
|
|
8669
8681
|
});
|
|
8670
8682
|
this._scheduledSignal = null;
|
|
8671
8683
|
}
|
|
@@ -8697,7 +8709,8 @@ class ClientStrategy {
|
|
|
8697
8709
|
* // Scheduled signal becomes pending signal immediately
|
|
8698
8710
|
* ```
|
|
8699
8711
|
*/
|
|
8700
|
-
async activateScheduled(symbol, backtest,
|
|
8712
|
+
async activateScheduled(symbol, backtest, payload) {
|
|
8713
|
+
const activateId = payload.id;
|
|
8701
8714
|
this.params.logger.debug("ClientStrategy activateScheduled", {
|
|
8702
8715
|
symbol,
|
|
8703
8716
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8715,6 +8728,7 @@ class ClientStrategy {
|
|
|
8715
8728
|
if (this._scheduledSignal) {
|
|
8716
8729
|
this._activatedSignal = Object.assign({}, this._scheduledSignal, {
|
|
8717
8730
|
activateId,
|
|
8731
|
+
activateNote: payload.note,
|
|
8718
8732
|
});
|
|
8719
8733
|
this._scheduledSignal = null;
|
|
8720
8734
|
}
|
|
@@ -8746,7 +8760,8 @@ class ClientStrategy {
|
|
|
8746
8760
|
* // Strategy continues, can generate new signals
|
|
8747
8761
|
* ```
|
|
8748
8762
|
*/
|
|
8749
|
-
async closePending(symbol, backtest,
|
|
8763
|
+
async closePending(symbol, backtest, payload) {
|
|
8764
|
+
const closeId = payload.id;
|
|
8750
8765
|
this.params.logger.debug("ClientStrategy closePending", {
|
|
8751
8766
|
symbol,
|
|
8752
8767
|
hasPendingSignal: this._pendingSignal !== null,
|
|
@@ -8757,6 +8772,7 @@ class ClientStrategy {
|
|
|
8757
8772
|
if (this._pendingSignal) {
|
|
8758
8773
|
this._closedSignal = Object.assign({}, this._pendingSignal, {
|
|
8759
8774
|
closeId,
|
|
8775
|
+
closeNote: payload.note,
|
|
8760
8776
|
});
|
|
8761
8777
|
this._pendingSignal = null;
|
|
8762
8778
|
}
|
|
@@ -10020,6 +10036,8 @@ class MergeRisk {
|
|
|
10020
10036
|
}
|
|
10021
10037
|
}
|
|
10022
10038
|
|
|
10039
|
+
/** Default interval for strategies that do not specify one */
|
|
10040
|
+
const STRATEGY_DEFAULT_INTERVAL = "1m";
|
|
10023
10041
|
/**
|
|
10024
10042
|
* If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
|
|
10025
10043
|
* to the exchange (e.g. limit order failed to fill).
|
|
@@ -10405,7 +10423,7 @@ class StrategyConnectionService {
|
|
|
10405
10423
|
* @returns Configured ClientStrategy instance
|
|
10406
10424
|
*/
|
|
10407
10425
|
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10408
|
-
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10426
|
+
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10409
10427
|
return new ClientStrategy({
|
|
10410
10428
|
symbol,
|
|
10411
10429
|
interval,
|
|
@@ -11245,6 +11263,48 @@ class StrategyConnectionService {
|
|
|
11245
11263
|
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11246
11264
|
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11247
11265
|
};
|
|
11266
|
+
/**
|
|
11267
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
11268
|
+
*
|
|
11269
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11270
|
+
* ClientStrategy.getMaxDrawdownDistancePnlPercentage().
|
|
11271
|
+
* Returns null if no pending signal exists.
|
|
11272
|
+
*
|
|
11273
|
+
* @param backtest - Whether running in backtest mode
|
|
11274
|
+
* @param symbol - Trading pair symbol
|
|
11275
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11276
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
11277
|
+
*/
|
|
11278
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11279
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlPercentage", {
|
|
11280
|
+
symbol,
|
|
11281
|
+
context,
|
|
11282
|
+
});
|
|
11283
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11284
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11285
|
+
return await strategy.getMaxDrawdownDistancePnlPercentage(symbol, currentPrice);
|
|
11286
|
+
};
|
|
11287
|
+
/**
|
|
11288
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
11289
|
+
*
|
|
11290
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11291
|
+
* ClientStrategy.getMaxDrawdownDistancePnlCost().
|
|
11292
|
+
* Returns null if no pending signal exists.
|
|
11293
|
+
*
|
|
11294
|
+
* @param backtest - Whether running in backtest mode
|
|
11295
|
+
* @param symbol - Trading pair symbol
|
|
11296
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11297
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
11298
|
+
*/
|
|
11299
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
11300
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlCost", {
|
|
11301
|
+
symbol,
|
|
11302
|
+
context,
|
|
11303
|
+
});
|
|
11304
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11305
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11306
|
+
return await strategy.getMaxDrawdownDistancePnlCost(symbol, currentPrice);
|
|
11307
|
+
};
|
|
11248
11308
|
/**
|
|
11249
11309
|
* Disposes the ClientStrategy instance for the given context.
|
|
11250
11310
|
*
|
|
@@ -11304,17 +11364,17 @@ class StrategyConnectionService {
|
|
|
11304
11364
|
* @param backtest - Whether running in backtest mode
|
|
11305
11365
|
* @param symbol - Trading pair symbol
|
|
11306
11366
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
11307
|
-
* @param
|
|
11367
|
+
* @param payload - Optional commit payload with id and note
|
|
11308
11368
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
11309
11369
|
*/
|
|
11310
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
11370
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11311
11371
|
this.loggerService.log("strategyConnectionService cancelScheduled", {
|
|
11312
11372
|
symbol,
|
|
11313
11373
|
context,
|
|
11314
|
-
|
|
11374
|
+
payload,
|
|
11315
11375
|
});
|
|
11316
11376
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11317
|
-
await strategy.cancelScheduled(symbol, backtest,
|
|
11377
|
+
await strategy.cancelScheduled(symbol, backtest, payload);
|
|
11318
11378
|
};
|
|
11319
11379
|
/**
|
|
11320
11380
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -11329,17 +11389,17 @@ class StrategyConnectionService {
|
|
|
11329
11389
|
* @param backtest - Whether running in backtest mode
|
|
11330
11390
|
* @param symbol - Trading pair symbol
|
|
11331
11391
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
11332
|
-
* @param
|
|
11392
|
+
* @param payload - Optional commit payload with id and note
|
|
11333
11393
|
* @returns Promise that resolves when pending signal is closed
|
|
11334
11394
|
*/
|
|
11335
|
-
this.closePending = async (backtest, symbol, context,
|
|
11395
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
11336
11396
|
this.loggerService.log("strategyConnectionService closePending", {
|
|
11337
11397
|
symbol,
|
|
11338
11398
|
context,
|
|
11339
|
-
|
|
11399
|
+
payload,
|
|
11340
11400
|
});
|
|
11341
11401
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11342
|
-
await strategy.closePending(symbol, backtest,
|
|
11402
|
+
await strategy.closePending(symbol, backtest, payload);
|
|
11343
11403
|
};
|
|
11344
11404
|
/**
|
|
11345
11405
|
* Checks whether `partialProfit` would succeed without executing it.
|
|
@@ -11618,7 +11678,7 @@ class StrategyConnectionService {
|
|
|
11618
11678
|
* @param backtest - Whether running in backtest mode
|
|
11619
11679
|
* @param symbol - Trading pair symbol
|
|
11620
11680
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11621
|
-
* @param
|
|
11681
|
+
* @param payload - Optional commit payload with id and note
|
|
11622
11682
|
* @returns Promise that resolves when activation flag is set
|
|
11623
11683
|
*
|
|
11624
11684
|
* @example
|
|
@@ -11628,19 +11688,19 @@ class StrategyConnectionService {
|
|
|
11628
11688
|
* false,
|
|
11629
11689
|
* "BTCUSDT",
|
|
11630
11690
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
11631
|
-
* "manual-activation"
|
|
11691
|
+
* { id: "manual-activation" }
|
|
11632
11692
|
* );
|
|
11633
11693
|
* ```
|
|
11634
11694
|
*/
|
|
11635
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
11695
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11636
11696
|
this.loggerService.log("strategyConnectionService activateScheduled", {
|
|
11637
11697
|
symbol,
|
|
11638
11698
|
context,
|
|
11639
11699
|
backtest,
|
|
11640
|
-
|
|
11700
|
+
payload,
|
|
11641
11701
|
});
|
|
11642
11702
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11643
|
-
return await strategy.activateScheduled(symbol, backtest,
|
|
11703
|
+
return await strategy.activateScheduled(symbol, backtest, payload);
|
|
11644
11704
|
};
|
|
11645
11705
|
/**
|
|
11646
11706
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -14675,18 +14735,18 @@ class StrategyCoreService {
|
|
|
14675
14735
|
* @param backtest - Whether running in backtest mode
|
|
14676
14736
|
* @param symbol - Trading pair symbol
|
|
14677
14737
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
14678
|
-
* @param
|
|
14738
|
+
* @param payload - Optional commit payload with id and note
|
|
14679
14739
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
14680
14740
|
*/
|
|
14681
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
14741
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
14682
14742
|
this.loggerService.log("strategyCoreService cancelScheduled", {
|
|
14683
14743
|
symbol,
|
|
14684
14744
|
context,
|
|
14685
14745
|
backtest,
|
|
14686
|
-
|
|
14746
|
+
payload,
|
|
14687
14747
|
});
|
|
14688
14748
|
await this.validate(context);
|
|
14689
|
-
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context,
|
|
14749
|
+
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, payload);
|
|
14690
14750
|
};
|
|
14691
14751
|
/**
|
|
14692
14752
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -14702,18 +14762,18 @@ class StrategyCoreService {
|
|
|
14702
14762
|
* @param backtest - Whether running in backtest mode
|
|
14703
14763
|
* @param symbol - Trading pair symbol
|
|
14704
14764
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
14705
|
-
* @param
|
|
14765
|
+
* @param payload - Optional commit payload with id and note
|
|
14706
14766
|
* @returns Promise that resolves when pending signal is closed
|
|
14707
14767
|
*/
|
|
14708
|
-
this.closePending = async (backtest, symbol, context,
|
|
14768
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
14709
14769
|
this.loggerService.log("strategyCoreService closePending", {
|
|
14710
14770
|
symbol,
|
|
14711
14771
|
context,
|
|
14712
14772
|
backtest,
|
|
14713
|
-
|
|
14773
|
+
payload,
|
|
14714
14774
|
});
|
|
14715
14775
|
await this.validate(context);
|
|
14716
|
-
return await this.strategyConnectionService.closePending(backtest, symbol, context,
|
|
14776
|
+
return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
|
|
14717
14777
|
};
|
|
14718
14778
|
/**
|
|
14719
14779
|
* Disposes the ClientStrategy instance for the given context.
|
|
@@ -15058,7 +15118,7 @@ class StrategyCoreService {
|
|
|
15058
15118
|
* @param backtest - Whether running in backtest mode
|
|
15059
15119
|
* @param symbol - Trading pair symbol
|
|
15060
15120
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15061
|
-
* @param
|
|
15121
|
+
* @param payload - Optional commit payload with id and note
|
|
15062
15122
|
* @returns Promise that resolves when activation flag is set
|
|
15063
15123
|
*
|
|
15064
15124
|
* @example
|
|
@@ -15068,19 +15128,19 @@ class StrategyCoreService {
|
|
|
15068
15128
|
* false,
|
|
15069
15129
|
* "BTCUSDT",
|
|
15070
15130
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
15071
|
-
* "manual-activation"
|
|
15131
|
+
* { id: "manual-activation" }
|
|
15072
15132
|
* );
|
|
15073
15133
|
* ```
|
|
15074
15134
|
*/
|
|
15075
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
15135
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
15076
15136
|
this.loggerService.log("strategyCoreService activateScheduled", {
|
|
15077
15137
|
symbol,
|
|
15078
15138
|
context,
|
|
15079
15139
|
backtest,
|
|
15080
|
-
|
|
15140
|
+
payload,
|
|
15081
15141
|
});
|
|
15082
15142
|
await this.validate(context);
|
|
15083
|
-
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context,
|
|
15143
|
+
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, payload);
|
|
15084
15144
|
};
|
|
15085
15145
|
/**
|
|
15086
15146
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -15482,6 +15542,44 @@ class StrategyCoreService {
|
|
|
15482
15542
|
await this.validate(context);
|
|
15483
15543
|
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15484
15544
|
};
|
|
15545
|
+
/**
|
|
15546
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
15547
|
+
*
|
|
15548
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlPercentage().
|
|
15549
|
+
* Returns null if no pending signal exists.
|
|
15550
|
+
*
|
|
15551
|
+
* @param backtest - Whether running in backtest mode
|
|
15552
|
+
* @param symbol - Trading pair symbol
|
|
15553
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15554
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
15555
|
+
*/
|
|
15556
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15557
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlPercentage", {
|
|
15558
|
+
symbol,
|
|
15559
|
+
context,
|
|
15560
|
+
});
|
|
15561
|
+
await this.validate(context);
|
|
15562
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlPercentage(backtest, symbol, context);
|
|
15563
|
+
};
|
|
15564
|
+
/**
|
|
15565
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
15566
|
+
*
|
|
15567
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlCost().
|
|
15568
|
+
* Returns null if no pending signal exists.
|
|
15569
|
+
*
|
|
15570
|
+
* @param backtest - Whether running in backtest mode
|
|
15571
|
+
* @param symbol - Trading pair symbol
|
|
15572
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15573
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
15574
|
+
*/
|
|
15575
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
15576
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlCost", {
|
|
15577
|
+
symbol,
|
|
15578
|
+
context,
|
|
15579
|
+
});
|
|
15580
|
+
await this.validate(context);
|
|
15581
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlCost(backtest, symbol, context);
|
|
15582
|
+
};
|
|
15485
15583
|
}
|
|
15486
15584
|
}
|
|
15487
15585
|
|
|
@@ -16124,8 +16222,8 @@ class StrategySchemaService {
|
|
|
16124
16222
|
if (strategySchema.actions?.some((value) => typeof value !== "string")) {
|
|
16125
16223
|
throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
|
|
16126
16224
|
}
|
|
16127
|
-
if (typeof strategySchema.interval !== "string") {
|
|
16128
|
-
throw new Error(`strategy schema validation failed:
|
|
16225
|
+
if (strategySchema.interval && typeof strategySchema.interval !== "string") {
|
|
16226
|
+
throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
|
|
16129
16227
|
}
|
|
16130
16228
|
if (typeof strategySchema.getSignal !== "function") {
|
|
16131
16229
|
throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
|
|
@@ -18071,6 +18169,53 @@ class WalkerCommandService {
|
|
|
18071
18169
|
}
|
|
18072
18170
|
}
|
|
18073
18171
|
|
|
18172
|
+
/**
|
|
18173
|
+
* Converts markdown content to plain text with minimal formatting
|
|
18174
|
+
* @param content - Markdown string to convert
|
|
18175
|
+
* @returns Plain text representation
|
|
18176
|
+
*/
|
|
18177
|
+
const toPlainString = (content) => {
|
|
18178
|
+
if (!content) {
|
|
18179
|
+
return "";
|
|
18180
|
+
}
|
|
18181
|
+
let text = content;
|
|
18182
|
+
// Remove code blocks
|
|
18183
|
+
text = text.replace(/```[\s\S]*?```/g, "");
|
|
18184
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
18185
|
+
// Remove images
|
|
18186
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
18187
|
+
// Convert links to text only (keep link text, remove URL)
|
|
18188
|
+
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
18189
|
+
// Remove headers (convert to plain text)
|
|
18190
|
+
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
18191
|
+
// Remove bold and italic markers
|
|
18192
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
18193
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
18194
|
+
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
18195
|
+
text = text.replace(/___(.+?)___/g, "$1");
|
|
18196
|
+
text = text.replace(/__(.+?)__/g, "$1");
|
|
18197
|
+
text = text.replace(/_(.+?)_/g, "$1");
|
|
18198
|
+
// Remove strikethrough
|
|
18199
|
+
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
18200
|
+
// Convert lists to plain text with bullets
|
|
18201
|
+
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
18202
|
+
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
18203
|
+
// Remove blockquotes
|
|
18204
|
+
text = text.replace(/^\s*>\s+/gm, "");
|
|
18205
|
+
// Remove horizontal rules
|
|
18206
|
+
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
18207
|
+
// Remove HTML tags
|
|
18208
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
18209
|
+
// Remove excessive whitespace and normalize line breaks
|
|
18210
|
+
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
18211
|
+
text = text.replace(/[ \t]+/g, " ");
|
|
18212
|
+
// Remove all newline characters
|
|
18213
|
+
text = text.replace(/\n/g, " ");
|
|
18214
|
+
// Remove excessive spaces after newline removal
|
|
18215
|
+
text = text.replace(/\s+/g, " ");
|
|
18216
|
+
return text.trim();
|
|
18217
|
+
};
|
|
18218
|
+
|
|
18074
18219
|
/**
|
|
18075
18220
|
* Column configuration for backtest markdown reports.
|
|
18076
18221
|
*
|
|
@@ -18746,7 +18891,7 @@ const partial_columns = [
|
|
|
18746
18891
|
{
|
|
18747
18892
|
key: "note",
|
|
18748
18893
|
label: "Note",
|
|
18749
|
-
format: (data) => data.note
|
|
18894
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
18750
18895
|
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
18751
18896
|
},
|
|
18752
18897
|
{
|
|
@@ -18906,7 +19051,7 @@ const breakeven_columns = [
|
|
|
18906
19051
|
{
|
|
18907
19052
|
key: "note",
|
|
18908
19053
|
label: "Note",
|
|
18909
|
-
format: (data) => data.note
|
|
19054
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
18910
19055
|
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
18911
19056
|
},
|
|
18912
19057
|
{
|
|
@@ -29034,7 +29179,7 @@ class StrategyReportService {
|
|
|
29034
29179
|
/**
|
|
29035
29180
|
* Logs a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29036
29181
|
*/
|
|
29037
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId) => {
|
|
29182
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId, note) => {
|
|
29038
29183
|
this.loggerService.log("strategyReportService cancelScheduled", {
|
|
29039
29184
|
symbol,
|
|
29040
29185
|
isBacktest,
|
|
@@ -29047,6 +29192,7 @@ class StrategyReportService {
|
|
|
29047
29192
|
await ReportWriter.writeData("strategy", {
|
|
29048
29193
|
action: "cancel-scheduled",
|
|
29049
29194
|
cancelId,
|
|
29195
|
+
note,
|
|
29050
29196
|
symbol,
|
|
29051
29197
|
timestamp,
|
|
29052
29198
|
createdAt,
|
|
@@ -29068,7 +29214,7 @@ class StrategyReportService {
|
|
|
29068
29214
|
/**
|
|
29069
29215
|
* Logs a close-pending event when a pending signal is closed.
|
|
29070
29216
|
*/
|
|
29071
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId) => {
|
|
29217
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId, note) => {
|
|
29072
29218
|
this.loggerService.log("strategyReportService closePending", {
|
|
29073
29219
|
symbol,
|
|
29074
29220
|
isBacktest,
|
|
@@ -29081,6 +29227,7 @@ class StrategyReportService {
|
|
|
29081
29227
|
await ReportWriter.writeData("strategy", {
|
|
29082
29228
|
action: "close-pending",
|
|
29083
29229
|
closeId,
|
|
29230
|
+
note,
|
|
29084
29231
|
symbol,
|
|
29085
29232
|
timestamp,
|
|
29086
29233
|
createdAt,
|
|
@@ -29330,7 +29477,7 @@ class StrategyReportService {
|
|
|
29330
29477
|
/**
|
|
29331
29478
|
* Logs an activate-scheduled event when a scheduled signal is activated early.
|
|
29332
29479
|
*/
|
|
29333
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
29480
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
29334
29481
|
this.loggerService.log("strategyReportService activateScheduled", {
|
|
29335
29482
|
symbol,
|
|
29336
29483
|
currentPrice,
|
|
@@ -29344,6 +29491,7 @@ class StrategyReportService {
|
|
|
29344
29491
|
await ReportWriter.writeData("strategy", {
|
|
29345
29492
|
action: "activate-scheduled",
|
|
29346
29493
|
activateId,
|
|
29494
|
+
note,
|
|
29347
29495
|
currentPrice,
|
|
29348
29496
|
symbol,
|
|
29349
29497
|
timestamp,
|
|
@@ -29437,14 +29585,14 @@ class StrategyReportService {
|
|
|
29437
29585
|
exchangeName: event.exchangeName,
|
|
29438
29586
|
frameName: event.frameName,
|
|
29439
29587
|
strategyName: event.strategyName,
|
|
29440
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId));
|
|
29588
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId, event.note));
|
|
29441
29589
|
const unClosePending = strategyCommitSubject
|
|
29442
29590
|
.filter(({ action }) => action === "close-pending")
|
|
29443
29591
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
29444
29592
|
exchangeName: event.exchangeName,
|
|
29445
29593
|
frameName: event.frameName,
|
|
29446
29594
|
strategyName: event.strategyName,
|
|
29447
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId));
|
|
29595
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId, event.note));
|
|
29448
29596
|
const unPartialProfit = strategyCommitSubject
|
|
29449
29597
|
.filter(({ action }) => action === "partial-profit")
|
|
29450
29598
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -29486,7 +29634,7 @@ class StrategyReportService {
|
|
|
29486
29634
|
exchangeName: event.exchangeName,
|
|
29487
29635
|
frameName: event.frameName,
|
|
29488
29636
|
strategyName: event.strategyName,
|
|
29489
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
|
|
29637
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
|
|
29490
29638
|
const unAverageBuy = strategyCommitSubject
|
|
29491
29639
|
.filter(({ action }) => action === "average-buy")
|
|
29492
29640
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -30025,8 +30173,9 @@ class StrategyMarkdownService {
|
|
|
30025
30173
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30026
30174
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30027
30175
|
* @param cancelId - Optional identifier for the cancellation reason
|
|
30176
|
+
* @param note - Optional note from commit payload
|
|
30028
30177
|
*/
|
|
30029
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId) => {
|
|
30178
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId, note) => {
|
|
30030
30179
|
this.loggerService.log("strategyMarkdownService cancelScheduled", {
|
|
30031
30180
|
symbol,
|
|
30032
30181
|
isBacktest,
|
|
@@ -30047,6 +30196,7 @@ class StrategyMarkdownService {
|
|
|
30047
30196
|
action: "cancel-scheduled",
|
|
30048
30197
|
pnl,
|
|
30049
30198
|
cancelId,
|
|
30199
|
+
note,
|
|
30050
30200
|
createdAt,
|
|
30051
30201
|
backtest: isBacktest,
|
|
30052
30202
|
});
|
|
@@ -30059,8 +30209,9 @@ class StrategyMarkdownService {
|
|
|
30059
30209
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30060
30210
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30061
30211
|
* @param closeId - Optional identifier for the close reason
|
|
30212
|
+
* @param note - Optional note from commit payload
|
|
30062
30213
|
*/
|
|
30063
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId) => {
|
|
30214
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId, note) => {
|
|
30064
30215
|
this.loggerService.log("strategyMarkdownService closePending", {
|
|
30065
30216
|
symbol,
|
|
30066
30217
|
isBacktest,
|
|
@@ -30081,6 +30232,7 @@ class StrategyMarkdownService {
|
|
|
30081
30232
|
action: "close-pending",
|
|
30082
30233
|
pnl,
|
|
30083
30234
|
closeId,
|
|
30235
|
+
note,
|
|
30084
30236
|
createdAt,
|
|
30085
30237
|
backtest: isBacktest,
|
|
30086
30238
|
});
|
|
@@ -30379,8 +30531,9 @@ class StrategyMarkdownService {
|
|
|
30379
30531
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
30380
30532
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
30381
30533
|
* @param activateId - Optional identifier for the activation reason
|
|
30534
|
+
* @param note - Optional note from commit payload
|
|
30382
30535
|
*/
|
|
30383
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
30536
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
30384
30537
|
this.loggerService.log("strategyMarkdownService activateScheduled", {
|
|
30385
30538
|
symbol,
|
|
30386
30539
|
currentPrice,
|
|
@@ -30403,6 +30556,7 @@ class StrategyMarkdownService {
|
|
|
30403
30556
|
pnl,
|
|
30404
30557
|
totalPartials,
|
|
30405
30558
|
activateId,
|
|
30559
|
+
note,
|
|
30406
30560
|
currentPrice,
|
|
30407
30561
|
createdAt,
|
|
30408
30562
|
backtest: isBacktest,
|
|
@@ -30607,14 +30761,14 @@ class StrategyMarkdownService {
|
|
|
30607
30761
|
exchangeName: event.exchangeName,
|
|
30608
30762
|
frameName: event.frameName,
|
|
30609
30763
|
strategyName: event.strategyName,
|
|
30610
|
-
}, event.timestamp, event.signalId, event.pnl, event.cancelId));
|
|
30764
|
+
}, event.timestamp, event.signalId, event.pnl, event.cancelId, event.note));
|
|
30611
30765
|
const unClosePending = strategyCommitSubject
|
|
30612
30766
|
.filter(({ action }) => action === "close-pending")
|
|
30613
30767
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
30614
30768
|
exchangeName: event.exchangeName,
|
|
30615
30769
|
frameName: event.frameName,
|
|
30616
30770
|
strategyName: event.strategyName,
|
|
30617
|
-
}, event.timestamp, event.signalId, event.pnl, event.closeId));
|
|
30771
|
+
}, event.timestamp, event.signalId, event.pnl, event.closeId, event.note));
|
|
30618
30772
|
const unPartialProfit = strategyCommitSubject
|
|
30619
30773
|
.filter(({ action }) => action === "partial-profit")
|
|
30620
30774
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -30656,7 +30810,7 @@ class StrategyMarkdownService {
|
|
|
30656
30810
|
exchangeName: event.exchangeName,
|
|
30657
30811
|
frameName: event.frameName,
|
|
30658
30812
|
strategyName: event.strategyName,
|
|
30659
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
|
|
30813
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
|
|
30660
30814
|
const unAverageBuy = strategyCommitSubject
|
|
30661
30815
|
.filter(({ action }) => action === "average-buy")
|
|
30662
30816
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -35141,6 +35295,8 @@ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strateg
|
|
|
35141
35295
|
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35142
35296
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35143
35297
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
35298
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlPercentage";
|
|
35299
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlCost";
|
|
35144
35300
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
35145
35301
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
35146
35302
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -35156,7 +35312,7 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35156
35312
|
*
|
|
35157
35313
|
* @param symbol - Trading pair symbol
|
|
35158
35314
|
* @param strategyName - Strategy name
|
|
35159
|
-
* @param
|
|
35315
|
+
* @param payload - Optional commit payload with id and note
|
|
35160
35316
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
35161
35317
|
*
|
|
35162
35318
|
* @example
|
|
@@ -35164,13 +35320,13 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35164
35320
|
* import { commitCancelScheduled } from "backtest-kit";
|
|
35165
35321
|
*
|
|
35166
35322
|
* // Cancel scheduled signal with custom ID
|
|
35167
|
-
* await commitCancelScheduled("BTCUSDT", "manual-cancel-001");
|
|
35323
|
+
* await commitCancelScheduled("BTCUSDT", { id: "manual-cancel-001" });
|
|
35168
35324
|
* ```
|
|
35169
35325
|
*/
|
|
35170
|
-
async function commitCancelScheduled(symbol,
|
|
35326
|
+
async function commitCancelScheduled(symbol, payload = {}) {
|
|
35171
35327
|
backtest.loggerService.info(CANCEL_SCHEDULED_METHOD_NAME, {
|
|
35172
35328
|
symbol,
|
|
35173
|
-
|
|
35329
|
+
payload,
|
|
35174
35330
|
});
|
|
35175
35331
|
if (!ExecutionContextService.hasContext()) {
|
|
35176
35332
|
throw new Error("commitCancelScheduled requires an execution context");
|
|
@@ -35180,7 +35336,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35180
35336
|
}
|
|
35181
35337
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35182
35338
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35183
|
-
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35339
|
+
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35184
35340
|
}
|
|
35185
35341
|
/**
|
|
35186
35342
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -35192,7 +35348,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35192
35348
|
* Automatically detects backtest/live mode from execution context.
|
|
35193
35349
|
*
|
|
35194
35350
|
* @param symbol - Trading pair symbol
|
|
35195
|
-
* @param
|
|
35351
|
+
* @param payload - Optional commit payload with id and note
|
|
35196
35352
|
* @returns Promise that resolves when pending signal is closed
|
|
35197
35353
|
*
|
|
35198
35354
|
* @example
|
|
@@ -35200,13 +35356,13 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35200
35356
|
* import { commitClosePending } from "backtest-kit";
|
|
35201
35357
|
*
|
|
35202
35358
|
* // Close pending signal with custom ID
|
|
35203
|
-
* await commitClosePending("BTCUSDT", "manual-close-001");
|
|
35359
|
+
* await commitClosePending("BTCUSDT", { id: "manual-close-001" });
|
|
35204
35360
|
* ```
|
|
35205
35361
|
*/
|
|
35206
|
-
async function commitClosePending(symbol,
|
|
35362
|
+
async function commitClosePending(symbol, payload = {}) {
|
|
35207
35363
|
backtest.loggerService.info(CLOSE_PENDING_METHOD_NAME, {
|
|
35208
35364
|
symbol,
|
|
35209
|
-
|
|
35365
|
+
payload,
|
|
35210
35366
|
});
|
|
35211
35367
|
if (!ExecutionContextService.hasContext()) {
|
|
35212
35368
|
throw new Error("commitClosePending requires an execution context");
|
|
@@ -35216,7 +35372,7 @@ async function commitClosePending(symbol, closeId) {
|
|
|
35216
35372
|
}
|
|
35217
35373
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35218
35374
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35219
|
-
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35375
|
+
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35220
35376
|
}
|
|
35221
35377
|
/**
|
|
35222
35378
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -35679,7 +35835,7 @@ async function commitBreakeven(symbol) {
|
|
|
35679
35835
|
* Automatically detects backtest/live mode from execution context.
|
|
35680
35836
|
*
|
|
35681
35837
|
* @param symbol - Trading pair symbol
|
|
35682
|
-
* @param
|
|
35838
|
+
* @param payload - Optional commit payload with id and note
|
|
35683
35839
|
* @returns Promise that resolves when activation flag is set
|
|
35684
35840
|
*
|
|
35685
35841
|
* @example
|
|
@@ -35687,13 +35843,13 @@ async function commitBreakeven(symbol) {
|
|
|
35687
35843
|
* import { commitActivateScheduled } from "backtest-kit";
|
|
35688
35844
|
*
|
|
35689
35845
|
* // Activate scheduled signal early with custom ID
|
|
35690
|
-
* await commitActivateScheduled("BTCUSDT", "manual-activate-001");
|
|
35846
|
+
* await commitActivateScheduled("BTCUSDT", { id: "manual-activate-001" });
|
|
35691
35847
|
* ```
|
|
35692
35848
|
*/
|
|
35693
|
-
async function commitActivateScheduled(symbol,
|
|
35849
|
+
async function commitActivateScheduled(symbol, payload = {}) {
|
|
35694
35850
|
backtest.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
|
|
35695
35851
|
symbol,
|
|
35696
|
-
|
|
35852
|
+
payload,
|
|
35697
35853
|
});
|
|
35698
35854
|
if (!ExecutionContextService.hasContext()) {
|
|
35699
35855
|
throw new Error("commitActivateScheduled requires an execution context");
|
|
@@ -35703,7 +35859,7 @@ async function commitActivateScheduled(symbol, activateId) {
|
|
|
35703
35859
|
}
|
|
35704
35860
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35705
35861
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35706
|
-
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35862
|
+
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35707
35863
|
}
|
|
35708
35864
|
/**
|
|
35709
35865
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -36891,6 +37047,64 @@ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
|
36891
37047
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36892
37048
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36893
37049
|
}
|
|
37050
|
+
/**
|
|
37051
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
37052
|
+
*
|
|
37053
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
37054
|
+
* Returns null if no pending signal exists.
|
|
37055
|
+
*
|
|
37056
|
+
* @param symbol - Trading pair symbol
|
|
37057
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
37058
|
+
*
|
|
37059
|
+
* @example
|
|
37060
|
+
* ```typescript
|
|
37061
|
+
* import { getMaxDrawdownDistancePnlPercentage } from "backtest-kit";
|
|
37062
|
+
*
|
|
37063
|
+
* const dist = await getMaxDrawdownDistancePnlPercentage("BTCUSDT");
|
|
37064
|
+
* // e.g. 3.5 (peak was +3.5% above trough)
|
|
37065
|
+
* ```
|
|
37066
|
+
*/
|
|
37067
|
+
async function getMaxDrawdownDistancePnlPercentage(symbol) {
|
|
37068
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
37069
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37070
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires an execution context");
|
|
37071
|
+
}
|
|
37072
|
+
if (!MethodContextService.hasContext()) {
|
|
37073
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires a method context");
|
|
37074
|
+
}
|
|
37075
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37076
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37077
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37078
|
+
}
|
|
37079
|
+
/**
|
|
37080
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
37081
|
+
*
|
|
37082
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
37083
|
+
* Returns null if no pending signal exists.
|
|
37084
|
+
*
|
|
37085
|
+
* @param symbol - Trading pair symbol
|
|
37086
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
37087
|
+
*
|
|
37088
|
+
* @example
|
|
37089
|
+
* ```typescript
|
|
37090
|
+
* import { getMaxDrawdownDistancePnlCost } from "backtest-kit";
|
|
37091
|
+
*
|
|
37092
|
+
* const dist = await getMaxDrawdownDistancePnlCost("BTCUSDT");
|
|
37093
|
+
* // e.g. 7.2 (peak was $7.2 above trough)
|
|
37094
|
+
* ```
|
|
37095
|
+
*/
|
|
37096
|
+
async function getMaxDrawdownDistancePnlCost(symbol) {
|
|
37097
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
37098
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37099
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires an execution context");
|
|
37100
|
+
}
|
|
37101
|
+
if (!MethodContextService.hasContext()) {
|
|
37102
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires a method context");
|
|
37103
|
+
}
|
|
37104
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37105
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37106
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37107
|
+
}
|
|
36894
37108
|
/**
|
|
36895
37109
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36896
37110
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38564,6 +38778,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE =
|
|
|
38564
38778
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38565
38779
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38566
38780
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38781
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getMaxDrawdownDistancePnlPercentage";
|
|
38782
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "BacktestUtils.getMaxDrawdownDistancePnlCost";
|
|
38567
38783
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38568
38784
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38569
38785
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39945,6 +40161,62 @@ class BacktestUtils {
|
|
|
39945
40161
|
}
|
|
39946
40162
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39947
40163
|
};
|
|
40164
|
+
/**
|
|
40165
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
40166
|
+
*
|
|
40167
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
40168
|
+
* Returns null if no pending signal exists.
|
|
40169
|
+
*
|
|
40170
|
+
* @param symbol - Trading pair symbol
|
|
40171
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40172
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
40173
|
+
*/
|
|
40174
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
40175
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
40176
|
+
symbol,
|
|
40177
|
+
context,
|
|
40178
|
+
});
|
|
40179
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40180
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40181
|
+
{
|
|
40182
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40183
|
+
riskName &&
|
|
40184
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40185
|
+
riskList &&
|
|
40186
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40187
|
+
actions &&
|
|
40188
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40189
|
+
}
|
|
40190
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(true, symbol, context);
|
|
40191
|
+
};
|
|
40192
|
+
/**
|
|
40193
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
40194
|
+
*
|
|
40195
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
40196
|
+
* Returns null if no pending signal exists.
|
|
40197
|
+
*
|
|
40198
|
+
* @param symbol - Trading pair symbol
|
|
40199
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40200
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
40201
|
+
*/
|
|
40202
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
40203
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
40204
|
+
symbol,
|
|
40205
|
+
context,
|
|
40206
|
+
});
|
|
40207
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40208
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40209
|
+
{
|
|
40210
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40211
|
+
riskName &&
|
|
40212
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40213
|
+
riskList &&
|
|
40214
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40215
|
+
actions &&
|
|
40216
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40217
|
+
}
|
|
40218
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(true, symbol, context);
|
|
40219
|
+
};
|
|
39948
40220
|
/**
|
|
39949
40221
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39950
40222
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40079,7 +40351,7 @@ class BacktestUtils {
|
|
|
40079
40351
|
* @param symbol - Trading pair symbol
|
|
40080
40352
|
* @param strategyName - Strategy name
|
|
40081
40353
|
* @param context - Execution context with exchangeName and frameName
|
|
40082
|
-
* @param
|
|
40354
|
+
* @param payload - Optional commit payload with id and note
|
|
40083
40355
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
40084
40356
|
*
|
|
40085
40357
|
* @example
|
|
@@ -40089,14 +40361,14 @@ class BacktestUtils {
|
|
|
40089
40361
|
* exchangeName: "binance",
|
|
40090
40362
|
* frameName: "frame1",
|
|
40091
40363
|
* strategyName: "my-strategy"
|
|
40092
|
-
* }, "manual-cancel-001");
|
|
40364
|
+
* }, { id: "manual-cancel-001" });
|
|
40093
40365
|
* ```
|
|
40094
40366
|
*/
|
|
40095
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
40367
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
40096
40368
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
40097
40369
|
symbol,
|
|
40098
40370
|
context,
|
|
40099
|
-
|
|
40371
|
+
payload,
|
|
40100
40372
|
});
|
|
40101
40373
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
40102
40374
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -40109,7 +40381,7 @@ class BacktestUtils {
|
|
|
40109
40381
|
actions &&
|
|
40110
40382
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED));
|
|
40111
40383
|
}
|
|
40112
|
-
await backtest.strategyCoreService.cancelScheduled(true, symbol, context,
|
|
40384
|
+
await backtest.strategyCoreService.cancelScheduled(true, symbol, context, payload);
|
|
40113
40385
|
};
|
|
40114
40386
|
/**
|
|
40115
40387
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -40120,7 +40392,7 @@ class BacktestUtils {
|
|
|
40120
40392
|
*
|
|
40121
40393
|
* @param symbol - Trading pair symbol
|
|
40122
40394
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40123
|
-
* @param
|
|
40395
|
+
* @param payload - Optional commit payload with id and note
|
|
40124
40396
|
* @returns Promise that resolves when pending signal is closed
|
|
40125
40397
|
*
|
|
40126
40398
|
* @example
|
|
@@ -40130,14 +40402,14 @@ class BacktestUtils {
|
|
|
40130
40402
|
* exchangeName: "binance",
|
|
40131
40403
|
* strategyName: "my-strategy",
|
|
40132
40404
|
* frameName: "1m"
|
|
40133
|
-
* }, "manual-close-001");
|
|
40405
|
+
* }, { id: "manual-close-001" });
|
|
40134
40406
|
* ```
|
|
40135
40407
|
*/
|
|
40136
|
-
this.commitClosePending = async (symbol, context,
|
|
40408
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
40137
40409
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CLOSE_PENDING, {
|
|
40138
40410
|
symbol,
|
|
40139
40411
|
context,
|
|
40140
|
-
|
|
40412
|
+
payload,
|
|
40141
40413
|
});
|
|
40142
40414
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
40143
40415
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -40150,7 +40422,7 @@ class BacktestUtils {
|
|
|
40150
40422
|
actions &&
|
|
40151
40423
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CLOSE_PENDING));
|
|
40152
40424
|
}
|
|
40153
|
-
await backtest.strategyCoreService.closePending(true, symbol, context,
|
|
40425
|
+
await backtest.strategyCoreService.closePending(true, symbol, context, payload);
|
|
40154
40426
|
};
|
|
40155
40427
|
/**
|
|
40156
40428
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -40786,7 +41058,7 @@ class BacktestUtils {
|
|
|
40786
41058
|
*
|
|
40787
41059
|
* @param symbol - Trading pair symbol
|
|
40788
41060
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40789
|
-
* @param
|
|
41061
|
+
* @param payload - Optional commit payload with id and note
|
|
40790
41062
|
* @returns Promise that resolves when activation flag is set
|
|
40791
41063
|
*
|
|
40792
41064
|
* @example
|
|
@@ -40796,14 +41068,14 @@ class BacktestUtils {
|
|
|
40796
41068
|
* strategyName: "my-strategy",
|
|
40797
41069
|
* exchangeName: "binance",
|
|
40798
41070
|
* frameName: "1h"
|
|
40799
|
-
* }, "manual-activate-001");
|
|
41071
|
+
* }, { id: "manual-activate-001" });
|
|
40800
41072
|
* ```
|
|
40801
41073
|
*/
|
|
40802
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
41074
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
40803
41075
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
40804
41076
|
symbol,
|
|
40805
41077
|
context,
|
|
40806
|
-
|
|
41078
|
+
payload,
|
|
40807
41079
|
});
|
|
40808
41080
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
40809
41081
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -40816,7 +41088,7 @@ class BacktestUtils {
|
|
|
40816
41088
|
actions &&
|
|
40817
41089
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
40818
41090
|
}
|
|
40819
|
-
await backtest.strategyCoreService.activateScheduled(true, symbol, context,
|
|
41091
|
+
await backtest.strategyCoreService.activateScheduled(true, symbol, context, payload);
|
|
40820
41092
|
};
|
|
40821
41093
|
/**
|
|
40822
41094
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -41074,6 +41346,8 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "Li
|
|
|
41074
41346
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41075
41347
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41076
41348
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
41349
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getMaxDrawdownDistancePnlPercentage";
|
|
41350
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "LiveUtils.getMaxDrawdownDistancePnlCost";
|
|
41077
41351
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
41078
41352
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
41079
41353
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -42598,6 +42872,70 @@ class LiveUtils {
|
|
|
42598
42872
|
frameName: "",
|
|
42599
42873
|
});
|
|
42600
42874
|
};
|
|
42875
|
+
/**
|
|
42876
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
42877
|
+
*
|
|
42878
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
42879
|
+
* Returns null if no pending signal exists.
|
|
42880
|
+
*
|
|
42881
|
+
* @param symbol - Trading pair symbol
|
|
42882
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42883
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
42884
|
+
*/
|
|
42885
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
42886
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
42887
|
+
symbol,
|
|
42888
|
+
context,
|
|
42889
|
+
});
|
|
42890
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
42891
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
42892
|
+
{
|
|
42893
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42894
|
+
riskName &&
|
|
42895
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
42896
|
+
riskList &&
|
|
42897
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
42898
|
+
actions &&
|
|
42899
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
42900
|
+
}
|
|
42901
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(false, symbol, {
|
|
42902
|
+
strategyName: context.strategyName,
|
|
42903
|
+
exchangeName: context.exchangeName,
|
|
42904
|
+
frameName: "",
|
|
42905
|
+
});
|
|
42906
|
+
};
|
|
42907
|
+
/**
|
|
42908
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
42909
|
+
*
|
|
42910
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
42911
|
+
* Returns null if no pending signal exists.
|
|
42912
|
+
*
|
|
42913
|
+
* @param symbol - Trading pair symbol
|
|
42914
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42915
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
42916
|
+
*/
|
|
42917
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
42918
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
42919
|
+
symbol,
|
|
42920
|
+
context,
|
|
42921
|
+
});
|
|
42922
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
42923
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
42924
|
+
{
|
|
42925
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42926
|
+
riskName &&
|
|
42927
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
42928
|
+
riskList &&
|
|
42929
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
42930
|
+
actions &&
|
|
42931
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
42932
|
+
}
|
|
42933
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(false, symbol, {
|
|
42934
|
+
strategyName: context.strategyName,
|
|
42935
|
+
exchangeName: context.exchangeName,
|
|
42936
|
+
frameName: "",
|
|
42937
|
+
});
|
|
42938
|
+
};
|
|
42601
42939
|
/**
|
|
42602
42940
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
42603
42941
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -42739,7 +43077,7 @@ class LiveUtils {
|
|
|
42739
43077
|
* @param symbol - Trading pair symbol
|
|
42740
43078
|
* @param strategyName - Strategy name
|
|
42741
43079
|
* @param context - Execution context with exchangeName and frameName
|
|
42742
|
-
* @param
|
|
43080
|
+
* @param payload - Optional commit payload with id and note
|
|
42743
43081
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
42744
43082
|
*
|
|
42745
43083
|
* @example
|
|
@@ -42749,14 +43087,14 @@ class LiveUtils {
|
|
|
42749
43087
|
* exchangeName: "binance",
|
|
42750
43088
|
* frameName: "",
|
|
42751
43089
|
* strategyName: "my-strategy"
|
|
42752
|
-
* }, "manual-cancel-001");
|
|
43090
|
+
* }, { id: "manual-cancel-001" });
|
|
42753
43091
|
* ```
|
|
42754
43092
|
*/
|
|
42755
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
43093
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
42756
43094
|
backtest.loggerService.info(LIVE_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
42757
43095
|
symbol,
|
|
42758
43096
|
context,
|
|
42759
|
-
|
|
43097
|
+
payload,
|
|
42760
43098
|
});
|
|
42761
43099
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
42762
43100
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -42773,7 +43111,7 @@ class LiveUtils {
|
|
|
42773
43111
|
strategyName: context.strategyName,
|
|
42774
43112
|
exchangeName: context.exchangeName,
|
|
42775
43113
|
frameName: "",
|
|
42776
|
-
},
|
|
43114
|
+
}, payload);
|
|
42777
43115
|
};
|
|
42778
43116
|
/**
|
|
42779
43117
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -42784,7 +43122,7 @@ class LiveUtils {
|
|
|
42784
43122
|
*
|
|
42785
43123
|
* @param symbol - Trading pair symbol
|
|
42786
43124
|
* @param context - Execution context with strategyName and exchangeName
|
|
42787
|
-
* @param
|
|
43125
|
+
* @param payload - Optional commit payload with id and note
|
|
42788
43126
|
* @returns Promise that resolves when pending signal is closed
|
|
42789
43127
|
*
|
|
42790
43128
|
* @example
|
|
@@ -42793,14 +43131,14 @@ class LiveUtils {
|
|
|
42793
43131
|
* await Live.commitClose("BTCUSDT", {
|
|
42794
43132
|
* exchangeName: "binance",
|
|
42795
43133
|
* strategyName: "my-strategy"
|
|
42796
|
-
* }, "manual-close-001");
|
|
43134
|
+
* }, { id: "manual-close-001" });
|
|
42797
43135
|
* ```
|
|
42798
43136
|
*/
|
|
42799
|
-
this.commitClosePending = async (symbol, context,
|
|
43137
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
42800
43138
|
backtest.loggerService.info(LIVE_METHOD_NAME_CLOSE_PENDING, {
|
|
42801
43139
|
symbol,
|
|
42802
43140
|
context,
|
|
42803
|
-
|
|
43141
|
+
payload,
|
|
42804
43142
|
});
|
|
42805
43143
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
42806
43144
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -42817,7 +43155,7 @@ class LiveUtils {
|
|
|
42817
43155
|
strategyName: context.strategyName,
|
|
42818
43156
|
exchangeName: context.exchangeName,
|
|
42819
43157
|
frameName: "",
|
|
42820
|
-
},
|
|
43158
|
+
}, payload);
|
|
42821
43159
|
};
|
|
42822
43160
|
/**
|
|
42823
43161
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -43611,7 +43949,7 @@ class LiveUtils {
|
|
|
43611
43949
|
*
|
|
43612
43950
|
* @param symbol - Trading pair symbol
|
|
43613
43951
|
* @param context - Execution context with strategyName and exchangeName
|
|
43614
|
-
* @param
|
|
43952
|
+
* @param payload - Optional commit payload with id and note
|
|
43615
43953
|
* @returns Promise that resolves when activation flag is set
|
|
43616
43954
|
*
|
|
43617
43955
|
* @example
|
|
@@ -43620,14 +43958,14 @@ class LiveUtils {
|
|
|
43620
43958
|
* await Live.commitActivateScheduled("BTCUSDT", {
|
|
43621
43959
|
* strategyName: "my-strategy",
|
|
43622
43960
|
* exchangeName: "binance"
|
|
43623
|
-
* }, "manual-activate-001");
|
|
43961
|
+
* }, { id: "manual-activate-001" });
|
|
43624
43962
|
* ```
|
|
43625
43963
|
*/
|
|
43626
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
43964
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
43627
43965
|
backtest.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
43628
43966
|
symbol,
|
|
43629
43967
|
context,
|
|
43630
|
-
|
|
43968
|
+
payload,
|
|
43631
43969
|
});
|
|
43632
43970
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
43633
43971
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -43644,7 +43982,7 @@ class LiveUtils {
|
|
|
43644
43982
|
strategyName: context.strategyName,
|
|
43645
43983
|
exchangeName: context.exchangeName,
|
|
43646
43984
|
frameName: "",
|
|
43647
|
-
},
|
|
43985
|
+
}, payload);
|
|
43648
43986
|
};
|
|
43649
43987
|
/**
|
|
43650
43988
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -49715,6 +50053,741 @@ class MaxDrawdownUtils {
|
|
|
49715
50053
|
*/
|
|
49716
50054
|
const MaxDrawdown = new MaxDrawdownUtils();
|
|
49717
50055
|
|
|
50056
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT = "ReflectUtils.getPositionPnlPercent";
|
|
50057
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_COST = "ReflectUtils.getPositionPnlCost";
|
|
50058
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "ReflectUtils.getPositionHighestProfitPrice";
|
|
50059
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.getPositionHighestProfitTimestamp";
|
|
50060
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
|
|
50061
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
|
|
50062
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
|
|
50063
|
+
const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
|
|
50064
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
|
|
50065
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
|
|
50066
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "ReflectUtils.getPositionMaxDrawdownPrice";
|
|
50067
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "ReflectUtils.getPositionMaxDrawdownTimestamp";
|
|
50068
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionMaxDrawdownPnlPercentage";
|
|
50069
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionMaxDrawdownPnlCost";
|
|
50070
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
50071
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "ReflectUtils.getPositionHighestProfitDistancePnlCost";
|
|
50072
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
50073
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
50074
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getMaxDrawdownDistancePnlPercentage";
|
|
50075
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "ReflectUtils.getMaxDrawdownDistancePnlCost";
|
|
50076
|
+
/**
|
|
50077
|
+
* Utility class for real-time position reflection: PNL, peak profit, and drawdown queries.
|
|
50078
|
+
*
|
|
50079
|
+
* Provides unified access to strategyCoreService position state methods with logging
|
|
50080
|
+
* and full validation (strategy, exchange, frame, risk, actions).
|
|
50081
|
+
* Works for both live and backtest modes via the `backtest` parameter.
|
|
50082
|
+
* Exported as singleton instance for convenient usage.
|
|
50083
|
+
*
|
|
50084
|
+
* @example
|
|
50085
|
+
* ```typescript
|
|
50086
|
+
* import { Reflect } from "backtest-kit";
|
|
50087
|
+
*
|
|
50088
|
+
* // Get current unrealized PNL percentage
|
|
50089
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50090
|
+
* "BTCUSDT",
|
|
50091
|
+
* 45000,
|
|
50092
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50093
|
+
* );
|
|
50094
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50095
|
+
*
|
|
50096
|
+
* // Get peak profit reached
|
|
50097
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50098
|
+
* "BTCUSDT",
|
|
50099
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50100
|
+
* );
|
|
50101
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50102
|
+
* ```
|
|
50103
|
+
*/
|
|
50104
|
+
class ReflectUtils {
|
|
50105
|
+
constructor() {
|
|
50106
|
+
/**
|
|
50107
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
50108
|
+
*
|
|
50109
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50110
|
+
* Returns null if no pending signal exists.
|
|
50111
|
+
*
|
|
50112
|
+
* @param symbol - Trading pair symbol
|
|
50113
|
+
* @param currentPrice - Current market price
|
|
50114
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50115
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50116
|
+
* @returns Promise resolving to PNL percentage or null
|
|
50117
|
+
*
|
|
50118
|
+
* @example
|
|
50119
|
+
* ```typescript
|
|
50120
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50121
|
+
* "BTCUSDT",
|
|
50122
|
+
* 45000,
|
|
50123
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50124
|
+
* );
|
|
50125
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50126
|
+
* ```
|
|
50127
|
+
*/
|
|
50128
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50129
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
|
|
50130
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50131
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50132
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50133
|
+
{
|
|
50134
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50135
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50136
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50137
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50138
|
+
}
|
|
50139
|
+
return await backtest.strategyCoreService.getPositionPnlPercent(backtest$1, symbol, currentPrice, context);
|
|
50140
|
+
};
|
|
50141
|
+
/**
|
|
50142
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
50143
|
+
*
|
|
50144
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost.
|
|
50145
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50146
|
+
* Returns null if no pending signal exists.
|
|
50147
|
+
*
|
|
50148
|
+
* @param symbol - Trading pair symbol
|
|
50149
|
+
* @param currentPrice - Current market price
|
|
50150
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50151
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50152
|
+
* @returns Promise resolving to PNL in dollars or null
|
|
50153
|
+
*
|
|
50154
|
+
* @example
|
|
50155
|
+
* ```typescript
|
|
50156
|
+
* const pnlCost = await Reflect.getPositionPnlCost(
|
|
50157
|
+
* "BTCUSDT",
|
|
50158
|
+
* 45000,
|
|
50159
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50160
|
+
* );
|
|
50161
|
+
* console.log(`PNL: $${pnlCost}`);
|
|
50162
|
+
* ```
|
|
50163
|
+
*/
|
|
50164
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50165
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
|
|
50166
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50167
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50168
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50169
|
+
{
|
|
50170
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50171
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50172
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50173
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50174
|
+
}
|
|
50175
|
+
return await backtest.strategyCoreService.getPositionPnlCost(backtest$1, symbol, currentPrice, context);
|
|
50176
|
+
};
|
|
50177
|
+
/**
|
|
50178
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
50179
|
+
*
|
|
50180
|
+
* Returns null if no pending signal exists.
|
|
50181
|
+
*
|
|
50182
|
+
* @param symbol - Trading pair symbol
|
|
50183
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50184
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50185
|
+
* @returns Promise resolving to price or null
|
|
50186
|
+
*
|
|
50187
|
+
* @example
|
|
50188
|
+
* ```typescript
|
|
50189
|
+
* const peakPrice = await Reflect.getPositionHighestProfitPrice(
|
|
50190
|
+
* "BTCUSDT",
|
|
50191
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50192
|
+
* );
|
|
50193
|
+
* console.log(`Peak price: ${peakPrice}`);
|
|
50194
|
+
* ```
|
|
50195
|
+
*/
|
|
50196
|
+
this.getPositionHighestProfitPrice = async (symbol, context, backtest$1 = false) => {
|
|
50197
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, { symbol, context });
|
|
50198
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50199
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50200
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50201
|
+
{
|
|
50202
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50203
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50204
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50205
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50206
|
+
}
|
|
50207
|
+
return await backtest.strategyCoreService.getPositionHighestProfitPrice(backtest$1, symbol, context);
|
|
50208
|
+
};
|
|
50209
|
+
/**
|
|
50210
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
50211
|
+
*
|
|
50212
|
+
* Returns null if no pending signal exists.
|
|
50213
|
+
*
|
|
50214
|
+
* @param symbol - Trading pair symbol
|
|
50215
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50216
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50217
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
50218
|
+
*
|
|
50219
|
+
* @example
|
|
50220
|
+
* ```typescript
|
|
50221
|
+
* const ts = await Reflect.getPositionHighestProfitTimestamp(
|
|
50222
|
+
* "BTCUSDT",
|
|
50223
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50224
|
+
* );
|
|
50225
|
+
* console.log(`Peak at: ${new Date(ts).toISOString()}`);
|
|
50226
|
+
* ```
|
|
50227
|
+
*/
|
|
50228
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
50229
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, { symbol, context });
|
|
50230
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50231
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50232
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50233
|
+
{
|
|
50234
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50235
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50236
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50237
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50238
|
+
}
|
|
50239
|
+
return await backtest.strategyCoreService.getPositionHighestProfitTimestamp(backtest$1, symbol, context);
|
|
50240
|
+
};
|
|
50241
|
+
/**
|
|
50242
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
50243
|
+
*
|
|
50244
|
+
* Returns null if no pending signal exists.
|
|
50245
|
+
*
|
|
50246
|
+
* @param symbol - Trading pair symbol
|
|
50247
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50248
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50249
|
+
* @returns Promise resolving to PnL percentage or null
|
|
50250
|
+
*
|
|
50251
|
+
* @example
|
|
50252
|
+
* ```typescript
|
|
50253
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50254
|
+
* "BTCUSDT",
|
|
50255
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50256
|
+
* );
|
|
50257
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50258
|
+
* ```
|
|
50259
|
+
*/
|
|
50260
|
+
this.getPositionHighestPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50261
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, { symbol, context });
|
|
50262
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50263
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50264
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50265
|
+
{
|
|
50266
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50267
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50268
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50269
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50270
|
+
}
|
|
50271
|
+
return await backtest.strategyCoreService.getPositionHighestPnlPercentage(backtest$1, symbol, context);
|
|
50272
|
+
};
|
|
50273
|
+
/**
|
|
50274
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
50275
|
+
*
|
|
50276
|
+
* Returns null if no pending signal exists.
|
|
50277
|
+
*
|
|
50278
|
+
* @param symbol - Trading pair symbol
|
|
50279
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50280
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50281
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
50282
|
+
*
|
|
50283
|
+
* @example
|
|
50284
|
+
* ```typescript
|
|
50285
|
+
* const peakCost = await Reflect.getPositionHighestPnlCost(
|
|
50286
|
+
* "BTCUSDT",
|
|
50287
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50288
|
+
* );
|
|
50289
|
+
* console.log(`Peak PNL: $${peakCost}`);
|
|
50290
|
+
* ```
|
|
50291
|
+
*/
|
|
50292
|
+
this.getPositionHighestPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50293
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, { symbol, context });
|
|
50294
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50295
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50296
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50297
|
+
{
|
|
50298
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50299
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50300
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50301
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50302
|
+
}
|
|
50303
|
+
return await backtest.strategyCoreService.getPositionHighestPnlCost(backtest$1, symbol, context);
|
|
50304
|
+
};
|
|
50305
|
+
/**
|
|
50306
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
50307
|
+
*
|
|
50308
|
+
* Returns null if no pending signal exists.
|
|
50309
|
+
*
|
|
50310
|
+
* @param symbol - Trading pair symbol
|
|
50311
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50312
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50313
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
50314
|
+
*
|
|
50315
|
+
* @example
|
|
50316
|
+
* ```typescript
|
|
50317
|
+
* const wasReachable = await Reflect.getPositionHighestProfitBreakeven(
|
|
50318
|
+
* "BTCUSDT",
|
|
50319
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50320
|
+
* );
|
|
50321
|
+
* console.log(`Breakeven reachable at peak: ${wasReachable}`);
|
|
50322
|
+
* ```
|
|
50323
|
+
*/
|
|
50324
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context, backtest$1 = false) => {
|
|
50325
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, { symbol, context });
|
|
50326
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50327
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50328
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50329
|
+
{
|
|
50330
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50331
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50332
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
50333
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
50334
|
+
}
|
|
50335
|
+
return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
|
|
50336
|
+
};
|
|
50337
|
+
/**
|
|
50338
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
50339
|
+
*
|
|
50340
|
+
* Returns null if no pending signal exists.
|
|
50341
|
+
*
|
|
50342
|
+
* @param symbol - Trading pair symbol
|
|
50343
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50344
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50345
|
+
* @returns Promise resolving to minutes since highest profit price was recorded, or null
|
|
50346
|
+
*
|
|
50347
|
+
* @example
|
|
50348
|
+
* ```typescript
|
|
50349
|
+
* const minutes = await Reflect.getPositionDrawdownMinutes(
|
|
50350
|
+
* "BTCUSDT",
|
|
50351
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50352
|
+
* );
|
|
50353
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
50354
|
+
* ```
|
|
50355
|
+
*/
|
|
50356
|
+
this.getPositionDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
50357
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, { symbol, context });
|
|
50358
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50359
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50360
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50361
|
+
{
|
|
50362
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50363
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50364
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
50365
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
50366
|
+
}
|
|
50367
|
+
return await backtest.strategyCoreService.getPositionDrawdownMinutes(backtest$1, symbol, context);
|
|
50368
|
+
};
|
|
50369
|
+
/**
|
|
50370
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
50371
|
+
*
|
|
50372
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
50373
|
+
* pulling back from its peak profit level.
|
|
50374
|
+
* Returns null if no pending signal exists.
|
|
50375
|
+
*
|
|
50376
|
+
* @param symbol - Trading pair symbol
|
|
50377
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50378
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50379
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
50380
|
+
*
|
|
50381
|
+
* @example
|
|
50382
|
+
* ```typescript
|
|
50383
|
+
* const minutes = await Reflect.getPositionHighestProfitMinutes(
|
|
50384
|
+
* "BTCUSDT",
|
|
50385
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50386
|
+
* );
|
|
50387
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
50388
|
+
* ```
|
|
50389
|
+
*/
|
|
50390
|
+
this.getPositionHighestProfitMinutes = async (symbol, context, backtest$1 = false) => {
|
|
50391
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, { symbol, context });
|
|
50392
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50393
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50394
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50395
|
+
{
|
|
50396
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50397
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50398
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
50399
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
50400
|
+
}
|
|
50401
|
+
return await backtest.strategyCoreService.getPositionHighestProfitMinutes(backtest$1, symbol, context);
|
|
50402
|
+
};
|
|
50403
|
+
/**
|
|
50404
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
50405
|
+
*
|
|
50406
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
50407
|
+
* Zero when called at the exact moment the trough was set.
|
|
50408
|
+
* Returns null if no pending signal exists.
|
|
50409
|
+
*
|
|
50410
|
+
* @param symbol - Trading pair symbol
|
|
50411
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50412
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50413
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
50414
|
+
*
|
|
50415
|
+
* @example
|
|
50416
|
+
* ```typescript
|
|
50417
|
+
* const minutes = await Reflect.getPositionMaxDrawdownMinutes(
|
|
50418
|
+
* "BTCUSDT",
|
|
50419
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50420
|
+
* );
|
|
50421
|
+
* console.log(`Drawdown trough was ${minutes} minutes ago`);
|
|
50422
|
+
* ```
|
|
50423
|
+
*/
|
|
50424
|
+
this.getPositionMaxDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
50425
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, { symbol, context });
|
|
50426
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50427
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50428
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50429
|
+
{
|
|
50430
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50431
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50432
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
50433
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
50434
|
+
}
|
|
50435
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownMinutes(backtest$1, symbol, context);
|
|
50436
|
+
};
|
|
50437
|
+
/**
|
|
50438
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
50439
|
+
*
|
|
50440
|
+
* Returns null if no pending signal exists.
|
|
50441
|
+
*
|
|
50442
|
+
* @param symbol - Trading pair symbol
|
|
50443
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50444
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50445
|
+
* @returns Promise resolving to price or null
|
|
50446
|
+
*
|
|
50447
|
+
* @example
|
|
50448
|
+
* ```typescript
|
|
50449
|
+
* const troughPrice = await Reflect.getPositionMaxDrawdownPrice(
|
|
50450
|
+
* "BTCUSDT",
|
|
50451
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50452
|
+
* );
|
|
50453
|
+
* console.log(`Worst price: ${troughPrice}`);
|
|
50454
|
+
* ```
|
|
50455
|
+
*/
|
|
50456
|
+
this.getPositionMaxDrawdownPrice = async (symbol, context, backtest$1 = false) => {
|
|
50457
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, { symbol, context });
|
|
50458
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50459
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50460
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50461
|
+
{
|
|
50462
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50463
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50464
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
50465
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
50466
|
+
}
|
|
50467
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPrice(backtest$1, symbol, context);
|
|
50468
|
+
};
|
|
50469
|
+
/**
|
|
50470
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
50471
|
+
*
|
|
50472
|
+
* Returns null if no pending signal exists.
|
|
50473
|
+
*
|
|
50474
|
+
* @param symbol - Trading pair symbol
|
|
50475
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50476
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50477
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
50478
|
+
*
|
|
50479
|
+
* @example
|
|
50480
|
+
* ```typescript
|
|
50481
|
+
* const ts = await Reflect.getPositionMaxDrawdownTimestamp(
|
|
50482
|
+
* "BTCUSDT",
|
|
50483
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50484
|
+
* );
|
|
50485
|
+
* console.log(`Worst drawdown at: ${new Date(ts).toISOString()}`);
|
|
50486
|
+
* ```
|
|
50487
|
+
*/
|
|
50488
|
+
this.getPositionMaxDrawdownTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
50489
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, { symbol, context });
|
|
50490
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50491
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50492
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50493
|
+
{
|
|
50494
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50495
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50496
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
50497
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
50498
|
+
}
|
|
50499
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownTimestamp(backtest$1, symbol, context);
|
|
50500
|
+
};
|
|
50501
|
+
/**
|
|
50502
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
50503
|
+
*
|
|
50504
|
+
* Returns null if no pending signal exists.
|
|
50505
|
+
*
|
|
50506
|
+
* @param symbol - Trading pair symbol
|
|
50507
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50508
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50509
|
+
* @returns Promise resolving to PnL percentage or null
|
|
50510
|
+
*
|
|
50511
|
+
* @example
|
|
50512
|
+
* ```typescript
|
|
50513
|
+
* const worstPnl = await Reflect.getPositionMaxDrawdownPnlPercentage(
|
|
50514
|
+
* "BTCUSDT",
|
|
50515
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50516
|
+
* );
|
|
50517
|
+
* console.log(`Worst PNL: ${worstPnl}%`);
|
|
50518
|
+
* ```
|
|
50519
|
+
*/
|
|
50520
|
+
this.getPositionMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50521
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
50522
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50523
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50524
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50525
|
+
{
|
|
50526
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50527
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50528
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50529
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50530
|
+
}
|
|
50531
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
50532
|
+
};
|
|
50533
|
+
/**
|
|
50534
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
50535
|
+
*
|
|
50536
|
+
* Returns null if no pending signal exists.
|
|
50537
|
+
*
|
|
50538
|
+
* @param symbol - Trading pair symbol
|
|
50539
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50540
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50541
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
50542
|
+
*
|
|
50543
|
+
* @example
|
|
50544
|
+
* ```typescript
|
|
50545
|
+
* const worstCost = await Reflect.getPositionMaxDrawdownPnlCost(
|
|
50546
|
+
* "BTCUSDT",
|
|
50547
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50548
|
+
* );
|
|
50549
|
+
* console.log(`Worst PNL: $${worstCost}`);
|
|
50550
|
+
* ```
|
|
50551
|
+
*/
|
|
50552
|
+
this.getPositionMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50553
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
50554
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50555
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50556
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50557
|
+
{
|
|
50558
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50559
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50560
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
50561
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
50562
|
+
}
|
|
50563
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
50564
|
+
};
|
|
50565
|
+
/**
|
|
50566
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
50567
|
+
*
|
|
50568
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50569
|
+
*
|
|
50570
|
+
* @param symbol - Trading pair symbol
|
|
50571
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50572
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50573
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
50574
|
+
*
|
|
50575
|
+
* @example
|
|
50576
|
+
* ```typescript
|
|
50577
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
50578
|
+
* "BTCUSDT",
|
|
50579
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50580
|
+
* );
|
|
50581
|
+
* console.log(`Dropped ${distance}% from peak`);
|
|
50582
|
+
* ```
|
|
50583
|
+
*/
|
|
50584
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50585
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
50586
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50587
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50588
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50589
|
+
{
|
|
50590
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50591
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50592
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
50593
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
50594
|
+
}
|
|
50595
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(backtest$1, symbol, context);
|
|
50596
|
+
};
|
|
50597
|
+
/**
|
|
50598
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
50599
|
+
*
|
|
50600
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50601
|
+
*
|
|
50602
|
+
* @param symbol - Trading pair symbol
|
|
50603
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50604
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50605
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
50606
|
+
*
|
|
50607
|
+
* @example
|
|
50608
|
+
* ```typescript
|
|
50609
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlCost(
|
|
50610
|
+
* "BTCUSDT",
|
|
50611
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50612
|
+
* );
|
|
50613
|
+
* console.log(`Dropped $${distance} from peak`);
|
|
50614
|
+
* ```
|
|
50615
|
+
*/
|
|
50616
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50617
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, { symbol, context });
|
|
50618
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50619
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50620
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50621
|
+
{
|
|
50622
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50623
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50624
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
50625
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
50626
|
+
}
|
|
50627
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(backtest$1, symbol, context);
|
|
50628
|
+
};
|
|
50629
|
+
/**
|
|
50630
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
50631
|
+
*
|
|
50632
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50633
|
+
*
|
|
50634
|
+
* @param symbol - Trading pair symbol
|
|
50635
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50636
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50637
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL% (≥ 0) or null
|
|
50638
|
+
*
|
|
50639
|
+
* @example
|
|
50640
|
+
* ```typescript
|
|
50641
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlPercentage(
|
|
50642
|
+
* "BTCUSDT",
|
|
50643
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50644
|
+
* );
|
|
50645
|
+
* console.log(`${distance}% above worst trough`);
|
|
50646
|
+
* ```
|
|
50647
|
+
*/
|
|
50648
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50649
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
50650
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50651
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50652
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50653
|
+
{
|
|
50654
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50655
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50656
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50657
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50658
|
+
}
|
|
50659
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
50660
|
+
};
|
|
50661
|
+
/**
|
|
50662
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
50663
|
+
*
|
|
50664
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50665
|
+
*
|
|
50666
|
+
* @param symbol - Trading pair symbol
|
|
50667
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50668
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50669
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL cost (≥ 0) or null
|
|
50670
|
+
*
|
|
50671
|
+
* @example
|
|
50672
|
+
* ```typescript
|
|
50673
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlCost(
|
|
50674
|
+
* "BTCUSDT",
|
|
50675
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50676
|
+
* );
|
|
50677
|
+
* console.log(`$${distance} above worst trough`);
|
|
50678
|
+
* ```
|
|
50679
|
+
*/
|
|
50680
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50681
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
50682
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50683
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50684
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50685
|
+
{
|
|
50686
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50687
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50688
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
50689
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
50690
|
+
}
|
|
50691
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
50692
|
+
};
|
|
50693
|
+
/**
|
|
50694
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
50695
|
+
*
|
|
50696
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50697
|
+
*
|
|
50698
|
+
* @param symbol - Trading pair symbol
|
|
50699
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50700
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50701
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
50702
|
+
*
|
|
50703
|
+
* @example
|
|
50704
|
+
* ```typescript
|
|
50705
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlPercentage(
|
|
50706
|
+
* "BTCUSDT",
|
|
50707
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50708
|
+
* );
|
|
50709
|
+
* console.log(`Peak-to-trough: ${distance}%`);
|
|
50710
|
+
* ```
|
|
50711
|
+
*/
|
|
50712
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50713
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
50714
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50715
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50716
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50717
|
+
{
|
|
50718
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50719
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50720
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
50721
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
50722
|
+
}
|
|
50723
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(backtest$1, symbol, context);
|
|
50724
|
+
};
|
|
50725
|
+
/**
|
|
50726
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
50727
|
+
*
|
|
50728
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50729
|
+
*
|
|
50730
|
+
* @param symbol - Trading pair symbol
|
|
50731
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50732
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50733
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
50734
|
+
*
|
|
50735
|
+
* @example
|
|
50736
|
+
* ```typescript
|
|
50737
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlCost(
|
|
50738
|
+
* "BTCUSDT",
|
|
50739
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50740
|
+
* );
|
|
50741
|
+
* console.log(`Peak-to-trough: $${distance}`);
|
|
50742
|
+
* ```
|
|
50743
|
+
*/
|
|
50744
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50745
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, { symbol, context });
|
|
50746
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50747
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50748
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50749
|
+
{
|
|
50750
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50751
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50752
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
50753
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
50754
|
+
}
|
|
50755
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(backtest$1, symbol, context);
|
|
50756
|
+
};
|
|
50757
|
+
}
|
|
50758
|
+
}
|
|
50759
|
+
/**
|
|
50760
|
+
* Singleton instance of ReflectUtils for convenient position state queries.
|
|
50761
|
+
*
|
|
50762
|
+
* @example
|
|
50763
|
+
* ```typescript
|
|
50764
|
+
* import { Reflect } from "backtest-kit";
|
|
50765
|
+
*
|
|
50766
|
+
* // Real-time PNL
|
|
50767
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50768
|
+
* "BTCUSDT",
|
|
50769
|
+
* 45000,
|
|
50770
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50771
|
+
* );
|
|
50772
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50773
|
+
*
|
|
50774
|
+
* // Peak profit
|
|
50775
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50776
|
+
* "BTCUSDT",
|
|
50777
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50778
|
+
* );
|
|
50779
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50780
|
+
*
|
|
50781
|
+
* // Drawdown from peak
|
|
50782
|
+
* const drawdown = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
50783
|
+
* "BTCUSDT",
|
|
50784
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50785
|
+
* );
|
|
50786
|
+
* console.log(`Dropped ${drawdown}% from peak`);
|
|
50787
|
+
* ```
|
|
50788
|
+
*/
|
|
50789
|
+
const Reflect$1 = new ReflectUtils();
|
|
50790
|
+
|
|
49718
50791
|
/**
|
|
49719
50792
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
49720
50793
|
*
|
|
@@ -51645,6 +52718,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
51645
52718
|
pnlEntries: data.signal.pnl.pnlEntries,
|
|
51646
52719
|
scheduledAt: data.signal.scheduledAt,
|
|
51647
52720
|
currentPrice: data.currentPrice,
|
|
52721
|
+
note: data.signal.note,
|
|
51648
52722
|
createdAt: data.createdAt,
|
|
51649
52723
|
};
|
|
51650
52724
|
}
|
|
@@ -51674,6 +52748,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
51674
52748
|
duration: durationMin,
|
|
51675
52749
|
scheduledAt: data.signal.scheduledAt,
|
|
51676
52750
|
pendingAt: data.signal.pendingAt,
|
|
52751
|
+
note: data.signal.note,
|
|
51677
52752
|
createdAt: data.createdAt,
|
|
51678
52753
|
};
|
|
51679
52754
|
}
|
|
@@ -51710,6 +52785,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
51710
52785
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51711
52786
|
pnlCost: data.data.pnl.pnlCost,
|
|
51712
52787
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
52788
|
+
note: data.data.note,
|
|
51713
52789
|
scheduledAt: data.data.scheduledAt,
|
|
51714
52790
|
pendingAt: data.data.pendingAt,
|
|
51715
52791
|
createdAt: data.timestamp,
|
|
@@ -51745,6 +52821,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
51745
52821
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51746
52822
|
pnlCost: data.data.pnl.pnlCost,
|
|
51747
52823
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
52824
|
+
note: data.data.note,
|
|
51748
52825
|
scheduledAt: data.data.scheduledAt,
|
|
51749
52826
|
pendingAt: data.data.pendingAt,
|
|
51750
52827
|
createdAt: data.timestamp,
|
|
@@ -51779,6 +52856,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
|
51779
52856
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51780
52857
|
pnlCost: data.data.pnl.pnlCost,
|
|
51781
52858
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
52859
|
+
note: data.data.note,
|
|
51782
52860
|
scheduledAt: data.data.scheduledAt,
|
|
51783
52861
|
pendingAt: data.data.pendingAt,
|
|
51784
52862
|
createdAt: data.timestamp,
|
|
@@ -51820,6 +52898,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51820
52898
|
pnlEntries: data.pnl.pnlEntries,
|
|
51821
52899
|
scheduledAt: data.scheduledAt,
|
|
51822
52900
|
pendingAt: data.pendingAt,
|
|
52901
|
+
note: data.note,
|
|
51823
52902
|
createdAt: data.timestamp,
|
|
51824
52903
|
};
|
|
51825
52904
|
}
|
|
@@ -51852,6 +52931,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51852
52931
|
pnlEntries: data.pnl.pnlEntries,
|
|
51853
52932
|
scheduledAt: data.scheduledAt,
|
|
51854
52933
|
pendingAt: data.pendingAt,
|
|
52934
|
+
note: data.note,
|
|
51855
52935
|
createdAt: data.timestamp,
|
|
51856
52936
|
};
|
|
51857
52937
|
}
|
|
@@ -51883,6 +52963,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51883
52963
|
pnlEntries: data.pnl.pnlEntries,
|
|
51884
52964
|
scheduledAt: data.scheduledAt,
|
|
51885
52965
|
pendingAt: data.pendingAt,
|
|
52966
|
+
note: data.note,
|
|
51886
52967
|
createdAt: data.timestamp,
|
|
51887
52968
|
};
|
|
51888
52969
|
}
|
|
@@ -51915,6 +52996,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51915
52996
|
pnlEntries: data.pnl.pnlEntries,
|
|
51916
52997
|
scheduledAt: data.scheduledAt,
|
|
51917
52998
|
pendingAt: data.pendingAt,
|
|
52999
|
+
note: data.note,
|
|
51918
53000
|
createdAt: data.timestamp,
|
|
51919
53001
|
};
|
|
51920
53002
|
}
|
|
@@ -51947,6 +53029,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51947
53029
|
pnlEntries: data.pnl.pnlEntries,
|
|
51948
53030
|
scheduledAt: data.scheduledAt,
|
|
51949
53031
|
pendingAt: data.pendingAt,
|
|
53032
|
+
note: data.note,
|
|
51950
53033
|
createdAt: data.timestamp,
|
|
51951
53034
|
};
|
|
51952
53035
|
}
|
|
@@ -51979,6 +53062,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51979
53062
|
pnlEntries: data.pnl.pnlEntries,
|
|
51980
53063
|
scheduledAt: data.scheduledAt,
|
|
51981
53064
|
pendingAt: data.pendingAt,
|
|
53065
|
+
note: data.note,
|
|
51982
53066
|
createdAt: data.timestamp,
|
|
51983
53067
|
};
|
|
51984
53068
|
}
|
|
@@ -52012,6 +53096,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
52012
53096
|
pnlEntries: data.pnl.pnlEntries,
|
|
52013
53097
|
scheduledAt: data.scheduledAt,
|
|
52014
53098
|
pendingAt: data.pendingAt,
|
|
53099
|
+
note: data.note,
|
|
52015
53100
|
createdAt: data.timestamp,
|
|
52016
53101
|
};
|
|
52017
53102
|
}
|
|
@@ -52035,6 +53120,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
52035
53120
|
pnlPriceClose: data.pnl.priceClose,
|
|
52036
53121
|
pnlCost: data.pnl.pnlCost,
|
|
52037
53122
|
pnlEntries: data.pnl.pnlEntries,
|
|
53123
|
+
note: data.note,
|
|
52038
53124
|
createdAt: data.timestamp,
|
|
52039
53125
|
};
|
|
52040
53126
|
}
|
|
@@ -52058,6 +53144,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
52058
53144
|
pnlPriceClose: data.pnl.priceClose,
|
|
52059
53145
|
pnlCost: data.pnl.pnlCost,
|
|
52060
53146
|
pnlEntries: data.pnl.pnlEntries,
|
|
53147
|
+
note: data.note,
|
|
52061
53148
|
createdAt: data.timestamp,
|
|
52062
53149
|
};
|
|
52063
53150
|
}
|
|
@@ -52099,6 +53186,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
52099
53186
|
totalPartials: data.totalPartials,
|
|
52100
53187
|
scheduledAt: data.scheduledAt,
|
|
52101
53188
|
pendingAt: data.pendingAt,
|
|
53189
|
+
note: data.signal.note,
|
|
52102
53190
|
createdAt: data.timestamp,
|
|
52103
53191
|
};
|
|
52104
53192
|
}
|
|
@@ -52131,6 +53219,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
52131
53219
|
scheduledAt: data.scheduledAt,
|
|
52132
53220
|
pendingAt: data.pendingAt,
|
|
52133
53221
|
closeReason: data.closeReason,
|
|
53222
|
+
note: data.signal.note,
|
|
52134
53223
|
createdAt: data.timestamp,
|
|
52135
53224
|
};
|
|
52136
53225
|
}
|
|
@@ -53630,6 +54719,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
|
|
|
53630
54719
|
const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
53631
54720
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
53632
54721
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
54722
|
+
const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
|
|
53633
54723
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
53634
54724
|
const MS_PER_MINUTE$1 = 60000;
|
|
53635
54725
|
const INTERVAL_MINUTES$1 = {
|
|
@@ -53881,7 +54971,7 @@ class CacheFileInstance {
|
|
|
53881
54971
|
/**
|
|
53882
54972
|
* Clears the index counter.
|
|
53883
54973
|
*/
|
|
53884
|
-
static
|
|
54974
|
+
static resetCounter() {
|
|
53885
54975
|
CacheFileInstance._indexCounter = 0;
|
|
53886
54976
|
}
|
|
53887
54977
|
/**
|
|
@@ -54136,11 +55226,17 @@ class CacheUtils {
|
|
|
54136
55226
|
*/
|
|
54137
55227
|
this.clear = () => {
|
|
54138
55228
|
backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
|
|
54139
|
-
|
|
54140
|
-
|
|
54141
|
-
|
|
54142
|
-
|
|
54143
|
-
|
|
55229
|
+
this._getFnInstance.clear();
|
|
55230
|
+
this._getFileInstance.clear();
|
|
55231
|
+
};
|
|
55232
|
+
/**
|
|
55233
|
+
* Resets the CacheFileInstance index counter to zero.
|
|
55234
|
+
* This is useful when process.cwd() changes between strategy iterations to ensure
|
|
55235
|
+
* that new CacheFileInstance objects start with index 0 and do not collide with old instances.
|
|
55236
|
+
*/
|
|
55237
|
+
this.resetCounter = () => {
|
|
55238
|
+
backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
|
|
55239
|
+
CacheFileInstance.resetCounter();
|
|
54144
55240
|
};
|
|
54145
55241
|
}
|
|
54146
55242
|
}
|
|
@@ -54164,10 +55260,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
|
54164
55260
|
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
54165
55261
|
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
54166
55262
|
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
55263
|
+
const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
|
|
54167
55264
|
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
54168
55265
|
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
54169
55266
|
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
54170
55267
|
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
55268
|
+
const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
|
|
54171
55269
|
const MS_PER_MINUTE = 60000;
|
|
54172
55270
|
const INTERVAL_MINUTES = {
|
|
54173
55271
|
"1m": 1,
|
|
@@ -54234,6 +55332,8 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
|
54234
55332
|
*
|
|
54235
55333
|
* State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
|
|
54236
55334
|
*
|
|
55335
|
+
* @template F - Concrete function type
|
|
55336
|
+
*
|
|
54237
55337
|
* @example
|
|
54238
55338
|
* ```typescript
|
|
54239
55339
|
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
@@ -54249,30 +55349,34 @@ class IntervalFnInstance {
|
|
|
54249
55349
|
*
|
|
54250
55350
|
* @param fn - Function to fire once per interval
|
|
54251
55351
|
* @param interval - Candle interval that controls the firing boundary
|
|
55352
|
+
* @param key - Optional key generator for argument-based state separation.
|
|
55353
|
+
* Default: `([symbol]) => symbol`
|
|
54252
55354
|
*/
|
|
54253
|
-
constructor(fn, interval) {
|
|
55355
|
+
constructor(fn, interval, key = ([symbol]) => symbol) {
|
|
54254
55356
|
this.fn = fn;
|
|
54255
55357
|
this.interval = interval;
|
|
54256
|
-
|
|
55358
|
+
this.key = key;
|
|
55359
|
+
/** Stores the last aligned timestamp per context+symbol+args key. */
|
|
54257
55360
|
this._stateMap = new Map();
|
|
54258
55361
|
/**
|
|
54259
55362
|
* Execute the signal function with once-per-interval enforcement.
|
|
54260
55363
|
*
|
|
54261
55364
|
* Algorithm:
|
|
54262
55365
|
* 1. Align the current execution context `when` to the interval boundary.
|
|
54263
|
-
* 2.
|
|
54264
|
-
* 3.
|
|
55366
|
+
* 2. Build state key from context + key generator result.
|
|
55367
|
+
* 3. If the stored aligned timestamp for this key equals the current one → return `null`.
|
|
55368
|
+
* 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
|
|
54265
55369
|
* the signal. If it returns `null`, leave state unchanged so the next call retries.
|
|
54266
55370
|
*
|
|
54267
55371
|
* Requires active method context and execution context.
|
|
54268
55372
|
*
|
|
54269
|
-
* @param
|
|
55373
|
+
* @param args - Arguments forwarded to the wrapped function
|
|
54270
55374
|
* @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
54271
55375
|
* within the same interval or when `fn` itself returned `null`
|
|
54272
55376
|
* @throws Error if method context, execution context, or interval is missing
|
|
54273
55377
|
*/
|
|
54274
|
-
this.run = async (
|
|
54275
|
-
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, {
|
|
55378
|
+
this.run = async (...args) => {
|
|
55379
|
+
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
|
|
54276
55380
|
const step = INTERVAL_MINUTES[this.interval];
|
|
54277
55381
|
{
|
|
54278
55382
|
if (!MethodContextService.hasContext()) {
|
|
@@ -54286,15 +55390,16 @@ class IntervalFnInstance {
|
|
|
54286
55390
|
}
|
|
54287
55391
|
}
|
|
54288
55392
|
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
54289
|
-
const key = `${contextKey}:${symbol}`;
|
|
54290
55393
|
const currentWhen = backtest.executionContextService.context.when;
|
|
54291
55394
|
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
54292
|
-
|
|
55395
|
+
const argKey = this.key(args);
|
|
55396
|
+
const stateKey = `${contextKey}:${argKey}`;
|
|
55397
|
+
if (this._stateMap.get(stateKey) === currentAligned) {
|
|
54293
55398
|
return null;
|
|
54294
55399
|
}
|
|
54295
|
-
const result = await this.fn(
|
|
55400
|
+
const result = await this.fn.apply(null, args);
|
|
54296
55401
|
if (result !== null) {
|
|
54297
|
-
this._stateMap.set(
|
|
55402
|
+
this._stateMap.set(stateKey, currentAligned);
|
|
54298
55403
|
}
|
|
54299
55404
|
return result;
|
|
54300
55405
|
};
|
|
@@ -54315,6 +55420,28 @@ class IntervalFnInstance {
|
|
|
54315
55420
|
}
|
|
54316
55421
|
}
|
|
54317
55422
|
};
|
|
55423
|
+
/**
|
|
55424
|
+
* Garbage collect expired state entries.
|
|
55425
|
+
*
|
|
55426
|
+
* Removes all entries whose aligned timestamp differs from the current interval boundary.
|
|
55427
|
+
* Call this periodically to free memory from stale state entries.
|
|
55428
|
+
*
|
|
55429
|
+
* Requires active execution context to get current time.
|
|
55430
|
+
*
|
|
55431
|
+
* @returns Number of entries removed
|
|
55432
|
+
*/
|
|
55433
|
+
this.gc = () => {
|
|
55434
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
55435
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
55436
|
+
let removed = 0;
|
|
55437
|
+
for (const [key, storedAligned] of this._stateMap.entries()) {
|
|
55438
|
+
if (storedAligned !== currentAligned) {
|
|
55439
|
+
this._stateMap.delete(key);
|
|
55440
|
+
removed++;
|
|
55441
|
+
}
|
|
55442
|
+
}
|
|
55443
|
+
return removed;
|
|
55444
|
+
};
|
|
54318
55445
|
}
|
|
54319
55446
|
}
|
|
54320
55447
|
/**
|
|
@@ -54328,7 +55455,7 @@ class IntervalFnInstance {
|
|
|
54328
55455
|
*
|
|
54329
55456
|
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
54330
55457
|
*
|
|
54331
|
-
* @template
|
|
55458
|
+
* @template F - Concrete async function type
|
|
54332
55459
|
*
|
|
54333
55460
|
* @example
|
|
54334
55461
|
* ```typescript
|
|
@@ -54349,7 +55476,7 @@ class IntervalFileInstance {
|
|
|
54349
55476
|
* Resets the index counter to zero.
|
|
54350
55477
|
* Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
|
|
54351
55478
|
*/
|
|
54352
|
-
static
|
|
55479
|
+
static resetCounter() {
|
|
54353
55480
|
IntervalFileInstance._indexCounter = 0;
|
|
54354
55481
|
}
|
|
54355
55482
|
/**
|
|
@@ -54358,18 +55485,21 @@ class IntervalFileInstance {
|
|
|
54358
55485
|
* @param fn - Async signal function to fire once per interval
|
|
54359
55486
|
* @param interval - Candle interval that controls the firing boundary
|
|
54360
55487
|
* @param name - Human-readable bucket name used as the directory prefix
|
|
55488
|
+
* @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
|
|
55489
|
+
* Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
|
|
54361
55490
|
*/
|
|
54362
|
-
constructor(fn, interval, name) {
|
|
55491
|
+
constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
|
|
54363
55492
|
this.fn = fn;
|
|
54364
55493
|
this.interval = interval;
|
|
54365
55494
|
this.name = name;
|
|
55495
|
+
this.key = key;
|
|
54366
55496
|
/**
|
|
54367
55497
|
* Execute the async function with persistent once-per-interval enforcement.
|
|
54368
55498
|
*
|
|
54369
55499
|
* Algorithm:
|
|
54370
55500
|
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
54371
|
-
* 2. Align execution context `when` to interval boundary → `
|
|
54372
|
-
* 3. Build entity key
|
|
55501
|
+
* 2. Align execution context `when` to interval boundary → `alignedMs`.
|
|
55502
|
+
* 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
|
|
54373
55503
|
* 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
|
|
54374
55504
|
* 5. On hit — return `null` (interval already fired).
|
|
54375
55505
|
* 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
|
|
@@ -54377,12 +55507,13 @@ class IntervalFileInstance {
|
|
|
54377
55507
|
* Requires active method context and execution context.
|
|
54378
55508
|
*
|
|
54379
55509
|
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
55510
|
+
* @param args - Additional arguments forwarded to the wrapped function
|
|
54380
55511
|
* @returns The value on the first non-null fire, `null` if already fired this interval
|
|
54381
55512
|
* or if `fn` itself returned `null`
|
|
54382
55513
|
* @throws Error if method context, execution context, or interval is missing
|
|
54383
55514
|
*/
|
|
54384
|
-
this.run = async (
|
|
54385
|
-
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, {
|
|
55515
|
+
this.run = async (...args) => {
|
|
55516
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
54386
55517
|
const step = INTERVAL_MINUTES[this.interval];
|
|
54387
55518
|
{
|
|
54388
55519
|
if (!MethodContextService.hasContext()) {
|
|
@@ -54395,15 +55526,16 @@ class IntervalFileInstance {
|
|
|
54395
55526
|
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
54396
55527
|
}
|
|
54397
55528
|
}
|
|
55529
|
+
const [symbol, ...rest] = args;
|
|
54398
55530
|
const { when } = backtest.executionContextService.context;
|
|
54399
|
-
const
|
|
55531
|
+
const alignedMs = align(when.getTime(), this.interval);
|
|
54400
55532
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
54401
|
-
const entityKey =
|
|
55533
|
+
const entityKey = this.key([symbol, alignedMs, ...rest]);
|
|
54402
55534
|
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
54403
55535
|
if (cached !== null) {
|
|
54404
55536
|
return null;
|
|
54405
55537
|
}
|
|
54406
|
-
const result = await this.fn(
|
|
55538
|
+
const result = await this.fn.call(null, ...args);
|
|
54407
55539
|
if (result !== null) {
|
|
54408
55540
|
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
54409
55541
|
}
|
|
@@ -54444,12 +55576,12 @@ class IntervalUtils {
|
|
|
54444
55576
|
* Memoized factory to get or create an `IntervalFnInstance` for a function.
|
|
54445
55577
|
* Each function reference gets its own isolated instance.
|
|
54446
55578
|
*/
|
|
54447
|
-
this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
|
|
55579
|
+
this._getInstance = memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
|
|
54448
55580
|
/**
|
|
54449
55581
|
* Memoized factory to get or create an `IntervalFileInstance` for an async function.
|
|
54450
55582
|
* Each function reference gets its own isolated persistent instance.
|
|
54451
55583
|
*/
|
|
54452
|
-
this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
|
|
55584
|
+
this._getFileInstance = memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
|
|
54453
55585
|
/**
|
|
54454
55586
|
* Wrap a signal function with in-memory once-per-interval firing.
|
|
54455
55587
|
*
|
|
@@ -54461,21 +55593,30 @@ class IntervalUtils {
|
|
|
54461
55593
|
*
|
|
54462
55594
|
* @param run - Signal function to wrap
|
|
54463
55595
|
* @param context.interval - Candle interval that controls the firing boundary
|
|
54464
|
-
* @
|
|
55596
|
+
* @param context.key - Optional key generator for argument-based state separation
|
|
55597
|
+
* @returns Wrapped function with the same signature as `F`, plus a `clear()` method
|
|
54465
55598
|
*
|
|
54466
55599
|
* @example
|
|
54467
55600
|
* ```typescript
|
|
55601
|
+
* // Without extra args
|
|
54468
55602
|
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
54469
|
-
*
|
|
54470
55603
|
* await fireOnce("BTCUSDT"); // → T or null (fn called)
|
|
54471
55604
|
* await fireOnce("BTCUSDT"); // → null (same interval, skipped)
|
|
55605
|
+
*
|
|
55606
|
+
* // With extra args and key
|
|
55607
|
+
* const fireOnce = Interval.fn(mySignalFn, {
|
|
55608
|
+
* interval: "15m",
|
|
55609
|
+
* key: ([symbol, period]) => `${symbol}_${period}`,
|
|
55610
|
+
* });
|
|
55611
|
+
* await fireOnce("BTCUSDT", 14); // → T or null
|
|
55612
|
+
* await fireOnce("BTCUSDT", 28); // → T or null (separate state)
|
|
54472
55613
|
* ```
|
|
54473
55614
|
*/
|
|
54474
55615
|
this.fn = (run, context) => {
|
|
54475
55616
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
54476
|
-
const wrappedFn = (
|
|
54477
|
-
const instance = this._getInstance(run, context.interval);
|
|
54478
|
-
return instance.run(
|
|
55617
|
+
const wrappedFn = (...args) => {
|
|
55618
|
+
const instance = this._getInstance(run, context.interval, context.key);
|
|
55619
|
+
return instance.run(...args);
|
|
54479
55620
|
};
|
|
54480
55621
|
wrappedFn.clear = () => {
|
|
54481
55622
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
@@ -54489,6 +55630,14 @@ class IntervalUtils {
|
|
|
54489
55630
|
}
|
|
54490
55631
|
this._getInstance.get(run)?.clear();
|
|
54491
55632
|
};
|
|
55633
|
+
wrappedFn.gc = () => {
|
|
55634
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
|
|
55635
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55636
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
|
|
55637
|
+
return;
|
|
55638
|
+
}
|
|
55639
|
+
return this._getInstance.get(run)?.gc();
|
|
55640
|
+
};
|
|
54492
55641
|
return wrappedFn;
|
|
54493
55642
|
};
|
|
54494
55643
|
/**
|
|
@@ -54501,28 +55650,33 @@ class IntervalUtils {
|
|
|
54501
55650
|
* The `run` function reference is used as the memoization key for the underlying
|
|
54502
55651
|
* `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
|
|
54503
55652
|
*
|
|
54504
|
-
* @template
|
|
55653
|
+
* @template F - Concrete async function type
|
|
54505
55654
|
* @param run - Async signal function to wrap with persistent once-per-interval firing
|
|
54506
55655
|
* @param context.interval - Candle interval that controls the firing boundary
|
|
54507
55656
|
* @param context.name - Human-readable bucket name; becomes the directory prefix
|
|
54508
|
-
* @
|
|
54509
|
-
*
|
|
55657
|
+
* @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
|
|
55658
|
+
* Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
|
|
55659
|
+
* @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
|
|
54510
55660
|
*
|
|
54511
55661
|
* @example
|
|
54512
55662
|
* ```typescript
|
|
54513
|
-
* const fetchSignal = async (symbol: string,
|
|
54514
|
-
* const fireOnce = Interval.file(fetchSignal, {
|
|
54515
|
-
*
|
|
55663
|
+
* const fetchSignal = async (symbol: string, period: number) => { ... };
|
|
55664
|
+
* const fireOnce = Interval.file(fetchSignal, {
|
|
55665
|
+
* interval: "1h",
|
|
55666
|
+
* name: "fetchSignal",
|
|
55667
|
+
* key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
|
|
55668
|
+
* });
|
|
55669
|
+
* await fireOnce("BTCUSDT", 14);
|
|
54516
55670
|
* ```
|
|
54517
55671
|
*/
|
|
54518
55672
|
this.file = (run, context) => {
|
|
54519
55673
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
|
|
54520
55674
|
{
|
|
54521
|
-
this._getFileInstance(run, context.interval, context.name);
|
|
55675
|
+
this._getFileInstance(run, context.interval, context.name, context.key);
|
|
54522
55676
|
}
|
|
54523
|
-
const wrappedFn = (
|
|
54524
|
-
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
54525
|
-
return instance.run(
|
|
55677
|
+
const wrappedFn = (...args) => {
|
|
55678
|
+
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
55679
|
+
return instance.run(...args);
|
|
54526
55680
|
};
|
|
54527
55681
|
wrappedFn.clear = async () => {
|
|
54528
55682
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
@@ -54548,10 +55702,10 @@ class IntervalUtils {
|
|
|
54548
55702
|
this.dispose = (run) => {
|
|
54549
55703
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
|
|
54550
55704
|
this._getInstance.clear(run);
|
|
55705
|
+
this._getFileInstance.clear(run);
|
|
54551
55706
|
};
|
|
54552
55707
|
/**
|
|
54553
|
-
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects
|
|
54554
|
-
* resets the `IntervalFileInstance` index counter.
|
|
55708
|
+
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
|
|
54555
55709
|
* Call this when `process.cwd()` changes between strategy iterations
|
|
54556
55710
|
* so new instances are created with the updated base path.
|
|
54557
55711
|
*/
|
|
@@ -54559,7 +55713,15 @@ class IntervalUtils {
|
|
|
54559
55713
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
|
|
54560
55714
|
this._getInstance.clear();
|
|
54561
55715
|
this._getFileInstance.clear();
|
|
54562
|
-
|
|
55716
|
+
};
|
|
55717
|
+
/**
|
|
55718
|
+
* Resets the IntervalFileInstance index counter to zero.
|
|
55719
|
+
* This is useful when process.cwd() changes between strategy iterations to ensure
|
|
55720
|
+
* that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
|
|
55721
|
+
*/
|
|
55722
|
+
this.resetCounter = () => {
|
|
55723
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
|
|
55724
|
+
IntervalFileInstance.resetCounter();
|
|
54563
55725
|
};
|
|
54564
55726
|
}
|
|
54565
55727
|
}
|
|
@@ -55693,4 +56855,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
55693
56855
|
return !errors.length;
|
|
55694
56856
|
};
|
|
55695
56857
|
|
|
55696
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|
|
56858
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|