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.cjs
CHANGED
|
@@ -4147,53 +4147,6 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
4147
4147
|
};
|
|
4148
4148
|
};
|
|
4149
4149
|
|
|
4150
|
-
/**
|
|
4151
|
-
* Converts markdown content to plain text with minimal formatting
|
|
4152
|
-
* @param content - Markdown string to convert
|
|
4153
|
-
* @returns Plain text representation
|
|
4154
|
-
*/
|
|
4155
|
-
const toPlainString = (content) => {
|
|
4156
|
-
if (!content) {
|
|
4157
|
-
return "";
|
|
4158
|
-
}
|
|
4159
|
-
let text = content;
|
|
4160
|
-
// Remove code blocks
|
|
4161
|
-
text = text.replace(/```[\s\S]*?```/g, "");
|
|
4162
|
-
text = text.replace(/`([^`]+)`/g, "$1");
|
|
4163
|
-
// Remove images
|
|
4164
|
-
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
4165
|
-
// Convert links to text only (keep link text, remove URL)
|
|
4166
|
-
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
4167
|
-
// Remove headers (convert to plain text)
|
|
4168
|
-
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
4169
|
-
// Remove bold and italic markers
|
|
4170
|
-
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
4171
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
4172
|
-
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
4173
|
-
text = text.replace(/___(.+?)___/g, "$1");
|
|
4174
|
-
text = text.replace(/__(.+?)__/g, "$1");
|
|
4175
|
-
text = text.replace(/_(.+?)_/g, "$1");
|
|
4176
|
-
// Remove strikethrough
|
|
4177
|
-
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
4178
|
-
// Convert lists to plain text with bullets
|
|
4179
|
-
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
4180
|
-
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
4181
|
-
// Remove blockquotes
|
|
4182
|
-
text = text.replace(/^\s*>\s+/gm, "");
|
|
4183
|
-
// Remove horizontal rules
|
|
4184
|
-
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
4185
|
-
// Remove HTML tags
|
|
4186
|
-
text = text.replace(/<[^>]+>/g, "");
|
|
4187
|
-
// Remove excessive whitespace and normalize line breaks
|
|
4188
|
-
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
4189
|
-
text = text.replace(/[ \t]+/g, " ");
|
|
4190
|
-
// Remove all newline characters
|
|
4191
|
-
text = text.replace(/\n/g, " ");
|
|
4192
|
-
// Remove excessive spaces after newline removal
|
|
4193
|
-
text = text.replace(/\s+/g, " ");
|
|
4194
|
-
return text.trim();
|
|
4195
|
-
};
|
|
4196
|
-
|
|
4197
4150
|
/**
|
|
4198
4151
|
* Returns the total closed state of a position using costBasisAtClose snapshots.
|
|
4199
4152
|
*
|
|
@@ -4828,6 +4781,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4828
4781
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4829
4782
|
scheduledAt: publicSignal.scheduledAt,
|
|
4830
4783
|
pendingAt: publicSignal.pendingAt,
|
|
4784
|
+
note: publicSignal.note,
|
|
4831
4785
|
});
|
|
4832
4786
|
continue;
|
|
4833
4787
|
}
|
|
@@ -4855,6 +4809,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4855
4809
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4856
4810
|
scheduledAt: publicSignal.scheduledAt,
|
|
4857
4811
|
pendingAt: publicSignal.pendingAt,
|
|
4812
|
+
note: publicSignal.note,
|
|
4858
4813
|
});
|
|
4859
4814
|
continue;
|
|
4860
4815
|
}
|
|
@@ -4881,6 +4836,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4881
4836
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4882
4837
|
scheduledAt: publicSignal.scheduledAt,
|
|
4883
4838
|
pendingAt: publicSignal.pendingAt,
|
|
4839
|
+
note: publicSignal.note,
|
|
4884
4840
|
});
|
|
4885
4841
|
continue;
|
|
4886
4842
|
}
|
|
@@ -4908,6 +4864,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4908
4864
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4909
4865
|
scheduledAt: publicSignal.scheduledAt,
|
|
4910
4866
|
pendingAt: publicSignal.pendingAt,
|
|
4867
|
+
note: publicSignal.note,
|
|
4911
4868
|
});
|
|
4912
4869
|
continue;
|
|
4913
4870
|
}
|
|
@@ -4935,6 +4892,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4935
4892
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4936
4893
|
scheduledAt: publicSignal.scheduledAt,
|
|
4937
4894
|
pendingAt: publicSignal.pendingAt,
|
|
4895
|
+
note: publicSignal.note,
|
|
4938
4896
|
});
|
|
4939
4897
|
continue;
|
|
4940
4898
|
}
|
|
@@ -4964,6 +4922,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4964
4922
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4965
4923
|
scheduledAt: publicSignal.scheduledAt,
|
|
4966
4924
|
pendingAt: publicSignal.pendingAt,
|
|
4925
|
+
note: publicSignal.note,
|
|
4967
4926
|
});
|
|
4968
4927
|
continue;
|
|
4969
4928
|
}
|
|
@@ -5062,7 +5021,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
5062
5021
|
const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
|
|
5063
5022
|
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
5064
5023
|
const signal = await Promise.race([
|
|
5065
|
-
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
|
|
5024
|
+
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
|
|
5066
5025
|
functoolsKit.sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
|
|
5067
5026
|
]);
|
|
5068
5027
|
if (typeof signal === "symbol") {
|
|
@@ -5092,7 +5051,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
5092
5051
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5093
5052
|
priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
|
|
5094
5053
|
position: signal.position,
|
|
5095
|
-
note:
|
|
5054
|
+
note: signal.note || "",
|
|
5096
5055
|
priceTakeProfit: signal.priceTakeProfit,
|
|
5097
5056
|
priceStopLoss: signal.priceStopLoss,
|
|
5098
5057
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
@@ -5118,7 +5077,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
5118
5077
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5119
5078
|
priceOpen: signal.priceOpen,
|
|
5120
5079
|
position: signal.position,
|
|
5121
|
-
note:
|
|
5080
|
+
note: signal.note || "",
|
|
5122
5081
|
priceTakeProfit: signal.priceTakeProfit,
|
|
5123
5082
|
priceStopLoss: signal.priceStopLoss,
|
|
5124
5083
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
@@ -5143,7 +5102,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
5143
5102
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5144
5103
|
priceOpen: currentPrice,
|
|
5145
5104
|
...structuredClone(signal),
|
|
5146
|
-
note:
|
|
5105
|
+
note: signal.note || "",
|
|
5147
5106
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
5148
5107
|
symbol: self.params.execution.context.symbol,
|
|
5149
5108
|
exchangeName: self.params.method.context.exchangeName,
|
|
@@ -5890,6 +5849,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
5890
5849
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
5891
5850
|
originalPriceOpen: scheduled.priceOpen,
|
|
5892
5851
|
pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
|
|
5852
|
+
note: scheduled.note,
|
|
5893
5853
|
});
|
|
5894
5854
|
return null;
|
|
5895
5855
|
}
|
|
@@ -6633,7 +6593,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
6633
6593
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
6634
6594
|
return result;
|
|
6635
6595
|
};
|
|
6636
|
-
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
|
|
6596
|
+
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId, cancelNote) => {
|
|
6637
6597
|
self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
|
|
6638
6598
|
symbol: self.params.execution.context.symbol,
|
|
6639
6599
|
signalId: scheduled.id,
|
|
@@ -6658,6 +6618,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
6658
6618
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6659
6619
|
originalPriceOpen: scheduled.priceOpen,
|
|
6660
6620
|
pnl: toProfitLossDto(scheduled, averagePrice),
|
|
6621
|
+
note: cancelNote ?? scheduled.note,
|
|
6661
6622
|
});
|
|
6662
6623
|
}
|
|
6663
6624
|
await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6736,6 +6697,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
6736
6697
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6737
6698
|
originalPriceOpen: scheduled.priceOpen,
|
|
6738
6699
|
pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
|
|
6700
|
+
note: scheduled.note,
|
|
6739
6701
|
});
|
|
6740
6702
|
return false;
|
|
6741
6703
|
}
|
|
@@ -6823,6 +6785,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
|
|
|
6823
6785
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
6824
6786
|
originalPriceOpen: closedSignal.priceOpen,
|
|
6825
6787
|
pnl: toProfitLossDto(closedSignal, averagePrice),
|
|
6788
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
6826
6789
|
});
|
|
6827
6790
|
await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
6828
6791
|
await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6869,7 +6832,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6869
6832
|
if (self._cancelledSignal) {
|
|
6870
6833
|
// Сигнал был отменен через cancel() в onSchedulePing
|
|
6871
6834
|
const cancelId = self._cancelledSignal.cancelId;
|
|
6872
|
-
const
|
|
6835
|
+
const cancelNote = self._cancelledSignal.cancelNote;
|
|
6836
|
+
const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId, cancelNote);
|
|
6873
6837
|
return { outcome: "cancelled", result };
|
|
6874
6838
|
}
|
|
6875
6839
|
// КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
|
|
@@ -6923,6 +6887,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6923
6887
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
6924
6888
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
6925
6889
|
pnl: toProfitLossDto(activatedSignal, averagePrice),
|
|
6890
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
6926
6891
|
});
|
|
6927
6892
|
return { outcome: "pending" };
|
|
6928
6893
|
}
|
|
@@ -6954,6 +6919,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6954
6919
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
6955
6920
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
6956
6921
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
6922
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
6957
6923
|
});
|
|
6958
6924
|
await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
|
|
6959
6925
|
await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
|
|
@@ -8074,6 +8040,44 @@ class ClientStrategy {
|
|
|
8074
8040
|
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8075
8041
|
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8076
8042
|
}
|
|
8043
|
+
/**
|
|
8044
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
8045
|
+
*
|
|
8046
|
+
* Measures the total swing from the stored `_peak.pnlPercentage` to the stored `_fall.pnlPercentage`.
|
|
8047
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
8048
|
+
*
|
|
8049
|
+
* Returns null if no pending signal exists.
|
|
8050
|
+
*
|
|
8051
|
+
* @param symbol - Trading pair symbol
|
|
8052
|
+
* @param currentPrice - Current market price
|
|
8053
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
8054
|
+
*/
|
|
8055
|
+
async getMaxDrawdownDistancePnlPercentage(symbol, currentPrice) {
|
|
8056
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlPercentage", { symbol, currentPrice });
|
|
8057
|
+
if (!this._pendingSignal) {
|
|
8058
|
+
return null;
|
|
8059
|
+
}
|
|
8060
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8061
|
+
}
|
|
8062
|
+
/**
|
|
8063
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
8064
|
+
*
|
|
8065
|
+
* Measures the total swing from the stored `_peak.pnlCost` to the stored `_fall.pnlCost`.
|
|
8066
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
8067
|
+
*
|
|
8068
|
+
* Returns null if no pending signal exists.
|
|
8069
|
+
*
|
|
8070
|
+
* @param symbol - Trading pair symbol
|
|
8071
|
+
* @param currentPrice - Current market price
|
|
8072
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
8073
|
+
*/
|
|
8074
|
+
async getMaxDrawdownDistancePnlCost(symbol, currentPrice) {
|
|
8075
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlCost", { symbol, currentPrice });
|
|
8076
|
+
if (!this._pendingSignal) {
|
|
8077
|
+
return null;
|
|
8078
|
+
}
|
|
8079
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8080
|
+
}
|
|
8077
8081
|
/**
|
|
8078
8082
|
* Performs a single tick of strategy execution.
|
|
8079
8083
|
*
|
|
@@ -8138,6 +8142,7 @@ class ClientStrategy {
|
|
|
8138
8142
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8139
8143
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8140
8144
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8145
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8141
8146
|
});
|
|
8142
8147
|
// Call onCancel callback
|
|
8143
8148
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8191,6 +8196,7 @@ class ClientStrategy {
|
|
|
8191
8196
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8192
8197
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8193
8198
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8199
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8194
8200
|
});
|
|
8195
8201
|
// Call onClose callback
|
|
8196
8202
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8272,6 +8278,7 @@ class ClientStrategy {
|
|
|
8272
8278
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
8273
8279
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
8274
8280
|
pnl: toProfitLossDto(activatedSignal, currentPrice),
|
|
8281
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
8275
8282
|
});
|
|
8276
8283
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
8277
8284
|
}
|
|
@@ -8302,6 +8309,7 @@ class ClientStrategy {
|
|
|
8302
8309
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
8303
8310
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
8304
8311
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
8312
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
8305
8313
|
});
|
|
8306
8314
|
// Call onOpen callback
|
|
8307
8315
|
await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8437,6 +8445,7 @@ class ClientStrategy {
|
|
|
8437
8445
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8438
8446
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8439
8447
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8448
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8440
8449
|
});
|
|
8441
8450
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8442
8451
|
const cancelledResult = {
|
|
@@ -8491,6 +8500,7 @@ class ClientStrategy {
|
|
|
8491
8500
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8492
8501
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8493
8502
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8503
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8494
8504
|
});
|
|
8495
8505
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8496
8506
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
@@ -8674,7 +8684,8 @@ class ClientStrategy {
|
|
|
8674
8684
|
* // Strategy continues, can generate new signals
|
|
8675
8685
|
* ```
|
|
8676
8686
|
*/
|
|
8677
|
-
async cancelScheduled(symbol, backtest,
|
|
8687
|
+
async cancelScheduled(symbol, backtest, payload) {
|
|
8688
|
+
const cancelId = payload.id;
|
|
8678
8689
|
this.params.logger.debug("ClientStrategy cancelScheduled", {
|
|
8679
8690
|
symbol,
|
|
8680
8691
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8686,6 +8697,7 @@ class ClientStrategy {
|
|
|
8686
8697
|
if (this._scheduledSignal) {
|
|
8687
8698
|
this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
|
|
8688
8699
|
cancelId,
|
|
8700
|
+
cancelNote: payload.note,
|
|
8689
8701
|
});
|
|
8690
8702
|
this._scheduledSignal = null;
|
|
8691
8703
|
}
|
|
@@ -8717,7 +8729,8 @@ class ClientStrategy {
|
|
|
8717
8729
|
* // Scheduled signal becomes pending signal immediately
|
|
8718
8730
|
* ```
|
|
8719
8731
|
*/
|
|
8720
|
-
async activateScheduled(symbol, backtest,
|
|
8732
|
+
async activateScheduled(symbol, backtest, payload) {
|
|
8733
|
+
const activateId = payload.id;
|
|
8721
8734
|
this.params.logger.debug("ClientStrategy activateScheduled", {
|
|
8722
8735
|
symbol,
|
|
8723
8736
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8735,6 +8748,7 @@ class ClientStrategy {
|
|
|
8735
8748
|
if (this._scheduledSignal) {
|
|
8736
8749
|
this._activatedSignal = Object.assign({}, this._scheduledSignal, {
|
|
8737
8750
|
activateId,
|
|
8751
|
+
activateNote: payload.note,
|
|
8738
8752
|
});
|
|
8739
8753
|
this._scheduledSignal = null;
|
|
8740
8754
|
}
|
|
@@ -8766,7 +8780,8 @@ class ClientStrategy {
|
|
|
8766
8780
|
* // Strategy continues, can generate new signals
|
|
8767
8781
|
* ```
|
|
8768
8782
|
*/
|
|
8769
|
-
async closePending(symbol, backtest,
|
|
8783
|
+
async closePending(symbol, backtest, payload) {
|
|
8784
|
+
const closeId = payload.id;
|
|
8770
8785
|
this.params.logger.debug("ClientStrategy closePending", {
|
|
8771
8786
|
symbol,
|
|
8772
8787
|
hasPendingSignal: this._pendingSignal !== null,
|
|
@@ -8777,6 +8792,7 @@ class ClientStrategy {
|
|
|
8777
8792
|
if (this._pendingSignal) {
|
|
8778
8793
|
this._closedSignal = Object.assign({}, this._pendingSignal, {
|
|
8779
8794
|
closeId,
|
|
8795
|
+
closeNote: payload.note,
|
|
8780
8796
|
});
|
|
8781
8797
|
this._pendingSignal = null;
|
|
8782
8798
|
}
|
|
@@ -10040,6 +10056,8 @@ class MergeRisk {
|
|
|
10040
10056
|
}
|
|
10041
10057
|
}
|
|
10042
10058
|
|
|
10059
|
+
/** Default interval for strategies that do not specify one */
|
|
10060
|
+
const STRATEGY_DEFAULT_INTERVAL = "1m";
|
|
10043
10061
|
/**
|
|
10044
10062
|
* If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
|
|
10045
10063
|
* to the exchange (e.g. limit order failed to fill).
|
|
@@ -10425,7 +10443,7 @@ class StrategyConnectionService {
|
|
|
10425
10443
|
* @returns Configured ClientStrategy instance
|
|
10426
10444
|
*/
|
|
10427
10445
|
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10428
|
-
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10446
|
+
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10429
10447
|
return new ClientStrategy({
|
|
10430
10448
|
symbol,
|
|
10431
10449
|
interval,
|
|
@@ -11265,6 +11283,48 @@ class StrategyConnectionService {
|
|
|
11265
11283
|
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11266
11284
|
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11267
11285
|
};
|
|
11286
|
+
/**
|
|
11287
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
11288
|
+
*
|
|
11289
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11290
|
+
* ClientStrategy.getMaxDrawdownDistancePnlPercentage().
|
|
11291
|
+
* Returns null if no pending signal exists.
|
|
11292
|
+
*
|
|
11293
|
+
* @param backtest - Whether running in backtest mode
|
|
11294
|
+
* @param symbol - Trading pair symbol
|
|
11295
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11296
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
11297
|
+
*/
|
|
11298
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11299
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlPercentage", {
|
|
11300
|
+
symbol,
|
|
11301
|
+
context,
|
|
11302
|
+
});
|
|
11303
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11304
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11305
|
+
return await strategy.getMaxDrawdownDistancePnlPercentage(symbol, currentPrice);
|
|
11306
|
+
};
|
|
11307
|
+
/**
|
|
11308
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
11309
|
+
*
|
|
11310
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11311
|
+
* ClientStrategy.getMaxDrawdownDistancePnlCost().
|
|
11312
|
+
* Returns null if no pending signal exists.
|
|
11313
|
+
*
|
|
11314
|
+
* @param backtest - Whether running in backtest mode
|
|
11315
|
+
* @param symbol - Trading pair symbol
|
|
11316
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11317
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
11318
|
+
*/
|
|
11319
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
11320
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlCost", {
|
|
11321
|
+
symbol,
|
|
11322
|
+
context,
|
|
11323
|
+
});
|
|
11324
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11325
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11326
|
+
return await strategy.getMaxDrawdownDistancePnlCost(symbol, currentPrice);
|
|
11327
|
+
};
|
|
11268
11328
|
/**
|
|
11269
11329
|
* Disposes the ClientStrategy instance for the given context.
|
|
11270
11330
|
*
|
|
@@ -11324,17 +11384,17 @@ class StrategyConnectionService {
|
|
|
11324
11384
|
* @param backtest - Whether running in backtest mode
|
|
11325
11385
|
* @param symbol - Trading pair symbol
|
|
11326
11386
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
11327
|
-
* @param
|
|
11387
|
+
* @param payload - Optional commit payload with id and note
|
|
11328
11388
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
11329
11389
|
*/
|
|
11330
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
11390
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11331
11391
|
this.loggerService.log("strategyConnectionService cancelScheduled", {
|
|
11332
11392
|
symbol,
|
|
11333
11393
|
context,
|
|
11334
|
-
|
|
11394
|
+
payload,
|
|
11335
11395
|
});
|
|
11336
11396
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11337
|
-
await strategy.cancelScheduled(symbol, backtest,
|
|
11397
|
+
await strategy.cancelScheduled(symbol, backtest, payload);
|
|
11338
11398
|
};
|
|
11339
11399
|
/**
|
|
11340
11400
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -11349,17 +11409,17 @@ class StrategyConnectionService {
|
|
|
11349
11409
|
* @param backtest - Whether running in backtest mode
|
|
11350
11410
|
* @param symbol - Trading pair symbol
|
|
11351
11411
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
11352
|
-
* @param
|
|
11412
|
+
* @param payload - Optional commit payload with id and note
|
|
11353
11413
|
* @returns Promise that resolves when pending signal is closed
|
|
11354
11414
|
*/
|
|
11355
|
-
this.closePending = async (backtest, symbol, context,
|
|
11415
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
11356
11416
|
this.loggerService.log("strategyConnectionService closePending", {
|
|
11357
11417
|
symbol,
|
|
11358
11418
|
context,
|
|
11359
|
-
|
|
11419
|
+
payload,
|
|
11360
11420
|
});
|
|
11361
11421
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11362
|
-
await strategy.closePending(symbol, backtest,
|
|
11422
|
+
await strategy.closePending(symbol, backtest, payload);
|
|
11363
11423
|
};
|
|
11364
11424
|
/**
|
|
11365
11425
|
* Checks whether `partialProfit` would succeed without executing it.
|
|
@@ -11638,7 +11698,7 @@ class StrategyConnectionService {
|
|
|
11638
11698
|
* @param backtest - Whether running in backtest mode
|
|
11639
11699
|
* @param symbol - Trading pair symbol
|
|
11640
11700
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11641
|
-
* @param
|
|
11701
|
+
* @param payload - Optional commit payload with id and note
|
|
11642
11702
|
* @returns Promise that resolves when activation flag is set
|
|
11643
11703
|
*
|
|
11644
11704
|
* @example
|
|
@@ -11648,19 +11708,19 @@ class StrategyConnectionService {
|
|
|
11648
11708
|
* false,
|
|
11649
11709
|
* "BTCUSDT",
|
|
11650
11710
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
11651
|
-
* "manual-activation"
|
|
11711
|
+
* { id: "manual-activation" }
|
|
11652
11712
|
* );
|
|
11653
11713
|
* ```
|
|
11654
11714
|
*/
|
|
11655
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
11715
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11656
11716
|
this.loggerService.log("strategyConnectionService activateScheduled", {
|
|
11657
11717
|
symbol,
|
|
11658
11718
|
context,
|
|
11659
11719
|
backtest,
|
|
11660
|
-
|
|
11720
|
+
payload,
|
|
11661
11721
|
});
|
|
11662
11722
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11663
|
-
return await strategy.activateScheduled(symbol, backtest,
|
|
11723
|
+
return await strategy.activateScheduled(symbol, backtest, payload);
|
|
11664
11724
|
};
|
|
11665
11725
|
/**
|
|
11666
11726
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -14695,18 +14755,18 @@ class StrategyCoreService {
|
|
|
14695
14755
|
* @param backtest - Whether running in backtest mode
|
|
14696
14756
|
* @param symbol - Trading pair symbol
|
|
14697
14757
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
14698
|
-
* @param
|
|
14758
|
+
* @param payload - Optional commit payload with id and note
|
|
14699
14759
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
14700
14760
|
*/
|
|
14701
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
14761
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
14702
14762
|
this.loggerService.log("strategyCoreService cancelScheduled", {
|
|
14703
14763
|
symbol,
|
|
14704
14764
|
context,
|
|
14705
14765
|
backtest,
|
|
14706
|
-
|
|
14766
|
+
payload,
|
|
14707
14767
|
});
|
|
14708
14768
|
await this.validate(context);
|
|
14709
|
-
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context,
|
|
14769
|
+
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, payload);
|
|
14710
14770
|
};
|
|
14711
14771
|
/**
|
|
14712
14772
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -14722,18 +14782,18 @@ class StrategyCoreService {
|
|
|
14722
14782
|
* @param backtest - Whether running in backtest mode
|
|
14723
14783
|
* @param symbol - Trading pair symbol
|
|
14724
14784
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
14725
|
-
* @param
|
|
14785
|
+
* @param payload - Optional commit payload with id and note
|
|
14726
14786
|
* @returns Promise that resolves when pending signal is closed
|
|
14727
14787
|
*/
|
|
14728
|
-
this.closePending = async (backtest, symbol, context,
|
|
14788
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
14729
14789
|
this.loggerService.log("strategyCoreService closePending", {
|
|
14730
14790
|
symbol,
|
|
14731
14791
|
context,
|
|
14732
14792
|
backtest,
|
|
14733
|
-
|
|
14793
|
+
payload,
|
|
14734
14794
|
});
|
|
14735
14795
|
await this.validate(context);
|
|
14736
|
-
return await this.strategyConnectionService.closePending(backtest, symbol, context,
|
|
14796
|
+
return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
|
|
14737
14797
|
};
|
|
14738
14798
|
/**
|
|
14739
14799
|
* Disposes the ClientStrategy instance for the given context.
|
|
@@ -15078,7 +15138,7 @@ class StrategyCoreService {
|
|
|
15078
15138
|
* @param backtest - Whether running in backtest mode
|
|
15079
15139
|
* @param symbol - Trading pair symbol
|
|
15080
15140
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15081
|
-
* @param
|
|
15141
|
+
* @param payload - Optional commit payload with id and note
|
|
15082
15142
|
* @returns Promise that resolves when activation flag is set
|
|
15083
15143
|
*
|
|
15084
15144
|
* @example
|
|
@@ -15088,19 +15148,19 @@ class StrategyCoreService {
|
|
|
15088
15148
|
* false,
|
|
15089
15149
|
* "BTCUSDT",
|
|
15090
15150
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
15091
|
-
* "manual-activation"
|
|
15151
|
+
* { id: "manual-activation" }
|
|
15092
15152
|
* );
|
|
15093
15153
|
* ```
|
|
15094
15154
|
*/
|
|
15095
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
15155
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
15096
15156
|
this.loggerService.log("strategyCoreService activateScheduled", {
|
|
15097
15157
|
symbol,
|
|
15098
15158
|
context,
|
|
15099
15159
|
backtest,
|
|
15100
|
-
|
|
15160
|
+
payload,
|
|
15101
15161
|
});
|
|
15102
15162
|
await this.validate(context);
|
|
15103
|
-
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context,
|
|
15163
|
+
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, payload);
|
|
15104
15164
|
};
|
|
15105
15165
|
/**
|
|
15106
15166
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -15502,6 +15562,44 @@ class StrategyCoreService {
|
|
|
15502
15562
|
await this.validate(context);
|
|
15503
15563
|
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15504
15564
|
};
|
|
15565
|
+
/**
|
|
15566
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
15567
|
+
*
|
|
15568
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlPercentage().
|
|
15569
|
+
* Returns null if no pending signal exists.
|
|
15570
|
+
*
|
|
15571
|
+
* @param backtest - Whether running in backtest mode
|
|
15572
|
+
* @param symbol - Trading pair symbol
|
|
15573
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15574
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
15575
|
+
*/
|
|
15576
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15577
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlPercentage", {
|
|
15578
|
+
symbol,
|
|
15579
|
+
context,
|
|
15580
|
+
});
|
|
15581
|
+
await this.validate(context);
|
|
15582
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlPercentage(backtest, symbol, context);
|
|
15583
|
+
};
|
|
15584
|
+
/**
|
|
15585
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
15586
|
+
*
|
|
15587
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlCost().
|
|
15588
|
+
* Returns null if no pending signal exists.
|
|
15589
|
+
*
|
|
15590
|
+
* @param backtest - Whether running in backtest mode
|
|
15591
|
+
* @param symbol - Trading pair symbol
|
|
15592
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15593
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
15594
|
+
*/
|
|
15595
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
15596
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlCost", {
|
|
15597
|
+
symbol,
|
|
15598
|
+
context,
|
|
15599
|
+
});
|
|
15600
|
+
await this.validate(context);
|
|
15601
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlCost(backtest, symbol, context);
|
|
15602
|
+
};
|
|
15505
15603
|
}
|
|
15506
15604
|
}
|
|
15507
15605
|
|
|
@@ -16144,8 +16242,8 @@ class StrategySchemaService {
|
|
|
16144
16242
|
if (strategySchema.actions?.some((value) => typeof value !== "string")) {
|
|
16145
16243
|
throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
|
|
16146
16244
|
}
|
|
16147
|
-
if (typeof strategySchema.interval !== "string") {
|
|
16148
|
-
throw new Error(`strategy schema validation failed:
|
|
16245
|
+
if (strategySchema.interval && typeof strategySchema.interval !== "string") {
|
|
16246
|
+
throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
|
|
16149
16247
|
}
|
|
16150
16248
|
if (typeof strategySchema.getSignal !== "function") {
|
|
16151
16249
|
throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
|
|
@@ -18091,6 +18189,53 @@ class WalkerCommandService {
|
|
|
18091
18189
|
}
|
|
18092
18190
|
}
|
|
18093
18191
|
|
|
18192
|
+
/**
|
|
18193
|
+
* Converts markdown content to plain text with minimal formatting
|
|
18194
|
+
* @param content - Markdown string to convert
|
|
18195
|
+
* @returns Plain text representation
|
|
18196
|
+
*/
|
|
18197
|
+
const toPlainString = (content) => {
|
|
18198
|
+
if (!content) {
|
|
18199
|
+
return "";
|
|
18200
|
+
}
|
|
18201
|
+
let text = content;
|
|
18202
|
+
// Remove code blocks
|
|
18203
|
+
text = text.replace(/```[\s\S]*?```/g, "");
|
|
18204
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
18205
|
+
// Remove images
|
|
18206
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
18207
|
+
// Convert links to text only (keep link text, remove URL)
|
|
18208
|
+
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
18209
|
+
// Remove headers (convert to plain text)
|
|
18210
|
+
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
18211
|
+
// Remove bold and italic markers
|
|
18212
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
18213
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
18214
|
+
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
18215
|
+
text = text.replace(/___(.+?)___/g, "$1");
|
|
18216
|
+
text = text.replace(/__(.+?)__/g, "$1");
|
|
18217
|
+
text = text.replace(/_(.+?)_/g, "$1");
|
|
18218
|
+
// Remove strikethrough
|
|
18219
|
+
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
18220
|
+
// Convert lists to plain text with bullets
|
|
18221
|
+
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
18222
|
+
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
18223
|
+
// Remove blockquotes
|
|
18224
|
+
text = text.replace(/^\s*>\s+/gm, "");
|
|
18225
|
+
// Remove horizontal rules
|
|
18226
|
+
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
18227
|
+
// Remove HTML tags
|
|
18228
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
18229
|
+
// Remove excessive whitespace and normalize line breaks
|
|
18230
|
+
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
18231
|
+
text = text.replace(/[ \t]+/g, " ");
|
|
18232
|
+
// Remove all newline characters
|
|
18233
|
+
text = text.replace(/\n/g, " ");
|
|
18234
|
+
// Remove excessive spaces after newline removal
|
|
18235
|
+
text = text.replace(/\s+/g, " ");
|
|
18236
|
+
return text.trim();
|
|
18237
|
+
};
|
|
18238
|
+
|
|
18094
18239
|
/**
|
|
18095
18240
|
* Column configuration for backtest markdown reports.
|
|
18096
18241
|
*
|
|
@@ -18766,7 +18911,7 @@ const partial_columns = [
|
|
|
18766
18911
|
{
|
|
18767
18912
|
key: "note",
|
|
18768
18913
|
label: "Note",
|
|
18769
|
-
format: (data) => data.note
|
|
18914
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
18770
18915
|
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
18771
18916
|
},
|
|
18772
18917
|
{
|
|
@@ -18926,7 +19071,7 @@ const breakeven_columns = [
|
|
|
18926
19071
|
{
|
|
18927
19072
|
key: "note",
|
|
18928
19073
|
label: "Note",
|
|
18929
|
-
format: (data) => data.note
|
|
19074
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
18930
19075
|
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
18931
19076
|
},
|
|
18932
19077
|
{
|
|
@@ -29054,7 +29199,7 @@ class StrategyReportService {
|
|
|
29054
29199
|
/**
|
|
29055
29200
|
* Logs a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29056
29201
|
*/
|
|
29057
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId) => {
|
|
29202
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId, note) => {
|
|
29058
29203
|
this.loggerService.log("strategyReportService cancelScheduled", {
|
|
29059
29204
|
symbol,
|
|
29060
29205
|
isBacktest,
|
|
@@ -29067,6 +29212,7 @@ class StrategyReportService {
|
|
|
29067
29212
|
await ReportWriter.writeData("strategy", {
|
|
29068
29213
|
action: "cancel-scheduled",
|
|
29069
29214
|
cancelId,
|
|
29215
|
+
note,
|
|
29070
29216
|
symbol,
|
|
29071
29217
|
timestamp,
|
|
29072
29218
|
createdAt,
|
|
@@ -29088,7 +29234,7 @@ class StrategyReportService {
|
|
|
29088
29234
|
/**
|
|
29089
29235
|
* Logs a close-pending event when a pending signal is closed.
|
|
29090
29236
|
*/
|
|
29091
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId) => {
|
|
29237
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId, note) => {
|
|
29092
29238
|
this.loggerService.log("strategyReportService closePending", {
|
|
29093
29239
|
symbol,
|
|
29094
29240
|
isBacktest,
|
|
@@ -29101,6 +29247,7 @@ class StrategyReportService {
|
|
|
29101
29247
|
await ReportWriter.writeData("strategy", {
|
|
29102
29248
|
action: "close-pending",
|
|
29103
29249
|
closeId,
|
|
29250
|
+
note,
|
|
29104
29251
|
symbol,
|
|
29105
29252
|
timestamp,
|
|
29106
29253
|
createdAt,
|
|
@@ -29350,7 +29497,7 @@ class StrategyReportService {
|
|
|
29350
29497
|
/**
|
|
29351
29498
|
* Logs an activate-scheduled event when a scheduled signal is activated early.
|
|
29352
29499
|
*/
|
|
29353
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
29500
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
29354
29501
|
this.loggerService.log("strategyReportService activateScheduled", {
|
|
29355
29502
|
symbol,
|
|
29356
29503
|
currentPrice,
|
|
@@ -29364,6 +29511,7 @@ class StrategyReportService {
|
|
|
29364
29511
|
await ReportWriter.writeData("strategy", {
|
|
29365
29512
|
action: "activate-scheduled",
|
|
29366
29513
|
activateId,
|
|
29514
|
+
note,
|
|
29367
29515
|
currentPrice,
|
|
29368
29516
|
symbol,
|
|
29369
29517
|
timestamp,
|
|
@@ -29457,14 +29605,14 @@ class StrategyReportService {
|
|
|
29457
29605
|
exchangeName: event.exchangeName,
|
|
29458
29606
|
frameName: event.frameName,
|
|
29459
29607
|
strategyName: event.strategyName,
|
|
29460
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId));
|
|
29608
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId, event.note));
|
|
29461
29609
|
const unClosePending = strategyCommitSubject
|
|
29462
29610
|
.filter(({ action }) => action === "close-pending")
|
|
29463
29611
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
29464
29612
|
exchangeName: event.exchangeName,
|
|
29465
29613
|
frameName: event.frameName,
|
|
29466
29614
|
strategyName: event.strategyName,
|
|
29467
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId));
|
|
29615
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId, event.note));
|
|
29468
29616
|
const unPartialProfit = strategyCommitSubject
|
|
29469
29617
|
.filter(({ action }) => action === "partial-profit")
|
|
29470
29618
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -29506,7 +29654,7 @@ class StrategyReportService {
|
|
|
29506
29654
|
exchangeName: event.exchangeName,
|
|
29507
29655
|
frameName: event.frameName,
|
|
29508
29656
|
strategyName: event.strategyName,
|
|
29509
|
-
}, 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));
|
|
29657
|
+
}, 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));
|
|
29510
29658
|
const unAverageBuy = strategyCommitSubject
|
|
29511
29659
|
.filter(({ action }) => action === "average-buy")
|
|
29512
29660
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -30045,8 +30193,9 @@ class StrategyMarkdownService {
|
|
|
30045
30193
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30046
30194
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30047
30195
|
* @param cancelId - Optional identifier for the cancellation reason
|
|
30196
|
+
* @param note - Optional note from commit payload
|
|
30048
30197
|
*/
|
|
30049
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId) => {
|
|
30198
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId, note) => {
|
|
30050
30199
|
this.loggerService.log("strategyMarkdownService cancelScheduled", {
|
|
30051
30200
|
symbol,
|
|
30052
30201
|
isBacktest,
|
|
@@ -30067,6 +30216,7 @@ class StrategyMarkdownService {
|
|
|
30067
30216
|
action: "cancel-scheduled",
|
|
30068
30217
|
pnl,
|
|
30069
30218
|
cancelId,
|
|
30219
|
+
note,
|
|
30070
30220
|
createdAt,
|
|
30071
30221
|
backtest: isBacktest,
|
|
30072
30222
|
});
|
|
@@ -30079,8 +30229,9 @@ class StrategyMarkdownService {
|
|
|
30079
30229
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30080
30230
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30081
30231
|
* @param closeId - Optional identifier for the close reason
|
|
30232
|
+
* @param note - Optional note from commit payload
|
|
30082
30233
|
*/
|
|
30083
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId) => {
|
|
30234
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId, note) => {
|
|
30084
30235
|
this.loggerService.log("strategyMarkdownService closePending", {
|
|
30085
30236
|
symbol,
|
|
30086
30237
|
isBacktest,
|
|
@@ -30101,6 +30252,7 @@ class StrategyMarkdownService {
|
|
|
30101
30252
|
action: "close-pending",
|
|
30102
30253
|
pnl,
|
|
30103
30254
|
closeId,
|
|
30255
|
+
note,
|
|
30104
30256
|
createdAt,
|
|
30105
30257
|
backtest: isBacktest,
|
|
30106
30258
|
});
|
|
@@ -30399,8 +30551,9 @@ class StrategyMarkdownService {
|
|
|
30399
30551
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
30400
30552
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
30401
30553
|
* @param activateId - Optional identifier for the activation reason
|
|
30554
|
+
* @param note - Optional note from commit payload
|
|
30402
30555
|
*/
|
|
30403
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
30556
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
30404
30557
|
this.loggerService.log("strategyMarkdownService activateScheduled", {
|
|
30405
30558
|
symbol,
|
|
30406
30559
|
currentPrice,
|
|
@@ -30423,6 +30576,7 @@ class StrategyMarkdownService {
|
|
|
30423
30576
|
pnl,
|
|
30424
30577
|
totalPartials,
|
|
30425
30578
|
activateId,
|
|
30579
|
+
note,
|
|
30426
30580
|
currentPrice,
|
|
30427
30581
|
createdAt,
|
|
30428
30582
|
backtest: isBacktest,
|
|
@@ -30627,14 +30781,14 @@ class StrategyMarkdownService {
|
|
|
30627
30781
|
exchangeName: event.exchangeName,
|
|
30628
30782
|
frameName: event.frameName,
|
|
30629
30783
|
strategyName: event.strategyName,
|
|
30630
|
-
}, event.timestamp, event.signalId, event.pnl, event.cancelId));
|
|
30784
|
+
}, event.timestamp, event.signalId, event.pnl, event.cancelId, event.note));
|
|
30631
30785
|
const unClosePending = strategyCommitSubject
|
|
30632
30786
|
.filter(({ action }) => action === "close-pending")
|
|
30633
30787
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
30634
30788
|
exchangeName: event.exchangeName,
|
|
30635
30789
|
frameName: event.frameName,
|
|
30636
30790
|
strategyName: event.strategyName,
|
|
30637
|
-
}, event.timestamp, event.signalId, event.pnl, event.closeId));
|
|
30791
|
+
}, event.timestamp, event.signalId, event.pnl, event.closeId, event.note));
|
|
30638
30792
|
const unPartialProfit = strategyCommitSubject
|
|
30639
30793
|
.filter(({ action }) => action === "partial-profit")
|
|
30640
30794
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -30676,7 +30830,7 @@ class StrategyMarkdownService {
|
|
|
30676
30830
|
exchangeName: event.exchangeName,
|
|
30677
30831
|
frameName: event.frameName,
|
|
30678
30832
|
strategyName: event.strategyName,
|
|
30679
|
-
}, 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));
|
|
30833
|
+
}, 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));
|
|
30680
30834
|
const unAverageBuy = strategyCommitSubject
|
|
30681
30835
|
.filter(({ action }) => action === "average-buy")
|
|
30682
30836
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -35161,6 +35315,8 @@ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strateg
|
|
|
35161
35315
|
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35162
35316
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35163
35317
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
35318
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlPercentage";
|
|
35319
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlCost";
|
|
35164
35320
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
35165
35321
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
35166
35322
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -35176,7 +35332,7 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35176
35332
|
*
|
|
35177
35333
|
* @param symbol - Trading pair symbol
|
|
35178
35334
|
* @param strategyName - Strategy name
|
|
35179
|
-
* @param
|
|
35335
|
+
* @param payload - Optional commit payload with id and note
|
|
35180
35336
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
35181
35337
|
*
|
|
35182
35338
|
* @example
|
|
@@ -35184,13 +35340,13 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35184
35340
|
* import { commitCancelScheduled } from "backtest-kit";
|
|
35185
35341
|
*
|
|
35186
35342
|
* // Cancel scheduled signal with custom ID
|
|
35187
|
-
* await commitCancelScheduled("BTCUSDT", "manual-cancel-001");
|
|
35343
|
+
* await commitCancelScheduled("BTCUSDT", { id: "manual-cancel-001" });
|
|
35188
35344
|
* ```
|
|
35189
35345
|
*/
|
|
35190
|
-
async function commitCancelScheduled(symbol,
|
|
35346
|
+
async function commitCancelScheduled(symbol, payload = {}) {
|
|
35191
35347
|
backtest.loggerService.info(CANCEL_SCHEDULED_METHOD_NAME, {
|
|
35192
35348
|
symbol,
|
|
35193
|
-
|
|
35349
|
+
payload,
|
|
35194
35350
|
});
|
|
35195
35351
|
if (!ExecutionContextService.hasContext()) {
|
|
35196
35352
|
throw new Error("commitCancelScheduled requires an execution context");
|
|
@@ -35200,7 +35356,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35200
35356
|
}
|
|
35201
35357
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35202
35358
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35203
|
-
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35359
|
+
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35204
35360
|
}
|
|
35205
35361
|
/**
|
|
35206
35362
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -35212,7 +35368,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35212
35368
|
* Automatically detects backtest/live mode from execution context.
|
|
35213
35369
|
*
|
|
35214
35370
|
* @param symbol - Trading pair symbol
|
|
35215
|
-
* @param
|
|
35371
|
+
* @param payload - Optional commit payload with id and note
|
|
35216
35372
|
* @returns Promise that resolves when pending signal is closed
|
|
35217
35373
|
*
|
|
35218
35374
|
* @example
|
|
@@ -35220,13 +35376,13 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35220
35376
|
* import { commitClosePending } from "backtest-kit";
|
|
35221
35377
|
*
|
|
35222
35378
|
* // Close pending signal with custom ID
|
|
35223
|
-
* await commitClosePending("BTCUSDT", "manual-close-001");
|
|
35379
|
+
* await commitClosePending("BTCUSDT", { id: "manual-close-001" });
|
|
35224
35380
|
* ```
|
|
35225
35381
|
*/
|
|
35226
|
-
async function commitClosePending(symbol,
|
|
35382
|
+
async function commitClosePending(symbol, payload = {}) {
|
|
35227
35383
|
backtest.loggerService.info(CLOSE_PENDING_METHOD_NAME, {
|
|
35228
35384
|
symbol,
|
|
35229
|
-
|
|
35385
|
+
payload,
|
|
35230
35386
|
});
|
|
35231
35387
|
if (!ExecutionContextService.hasContext()) {
|
|
35232
35388
|
throw new Error("commitClosePending requires an execution context");
|
|
@@ -35236,7 +35392,7 @@ async function commitClosePending(symbol, closeId) {
|
|
|
35236
35392
|
}
|
|
35237
35393
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35238
35394
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35239
|
-
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35395
|
+
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35240
35396
|
}
|
|
35241
35397
|
/**
|
|
35242
35398
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -35699,7 +35855,7 @@ async function commitBreakeven(symbol) {
|
|
|
35699
35855
|
* Automatically detects backtest/live mode from execution context.
|
|
35700
35856
|
*
|
|
35701
35857
|
* @param symbol - Trading pair symbol
|
|
35702
|
-
* @param
|
|
35858
|
+
* @param payload - Optional commit payload with id and note
|
|
35703
35859
|
* @returns Promise that resolves when activation flag is set
|
|
35704
35860
|
*
|
|
35705
35861
|
* @example
|
|
@@ -35707,13 +35863,13 @@ async function commitBreakeven(symbol) {
|
|
|
35707
35863
|
* import { commitActivateScheduled } from "backtest-kit";
|
|
35708
35864
|
*
|
|
35709
35865
|
* // Activate scheduled signal early with custom ID
|
|
35710
|
-
* await commitActivateScheduled("BTCUSDT", "manual-activate-001");
|
|
35866
|
+
* await commitActivateScheduled("BTCUSDT", { id: "manual-activate-001" });
|
|
35711
35867
|
* ```
|
|
35712
35868
|
*/
|
|
35713
|
-
async function commitActivateScheduled(symbol,
|
|
35869
|
+
async function commitActivateScheduled(symbol, payload = {}) {
|
|
35714
35870
|
backtest.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
|
|
35715
35871
|
symbol,
|
|
35716
|
-
|
|
35872
|
+
payload,
|
|
35717
35873
|
});
|
|
35718
35874
|
if (!ExecutionContextService.hasContext()) {
|
|
35719
35875
|
throw new Error("commitActivateScheduled requires an execution context");
|
|
@@ -35723,7 +35879,7 @@ async function commitActivateScheduled(symbol, activateId) {
|
|
|
35723
35879
|
}
|
|
35724
35880
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35725
35881
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35726
|
-
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35882
|
+
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35727
35883
|
}
|
|
35728
35884
|
/**
|
|
35729
35885
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -36911,6 +37067,64 @@ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
|
36911
37067
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36912
37068
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36913
37069
|
}
|
|
37070
|
+
/**
|
|
37071
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
37072
|
+
*
|
|
37073
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
37074
|
+
* Returns null if no pending signal exists.
|
|
37075
|
+
*
|
|
37076
|
+
* @param symbol - Trading pair symbol
|
|
37077
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
37078
|
+
*
|
|
37079
|
+
* @example
|
|
37080
|
+
* ```typescript
|
|
37081
|
+
* import { getMaxDrawdownDistancePnlPercentage } from "backtest-kit";
|
|
37082
|
+
*
|
|
37083
|
+
* const dist = await getMaxDrawdownDistancePnlPercentage("BTCUSDT");
|
|
37084
|
+
* // e.g. 3.5 (peak was +3.5% above trough)
|
|
37085
|
+
* ```
|
|
37086
|
+
*/
|
|
37087
|
+
async function getMaxDrawdownDistancePnlPercentage(symbol) {
|
|
37088
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
37089
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37090
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires an execution context");
|
|
37091
|
+
}
|
|
37092
|
+
if (!MethodContextService.hasContext()) {
|
|
37093
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires a method context");
|
|
37094
|
+
}
|
|
37095
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37096
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37097
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37098
|
+
}
|
|
37099
|
+
/**
|
|
37100
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
37101
|
+
*
|
|
37102
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
37103
|
+
* Returns null if no pending signal exists.
|
|
37104
|
+
*
|
|
37105
|
+
* @param symbol - Trading pair symbol
|
|
37106
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
37107
|
+
*
|
|
37108
|
+
* @example
|
|
37109
|
+
* ```typescript
|
|
37110
|
+
* import { getMaxDrawdownDistancePnlCost } from "backtest-kit";
|
|
37111
|
+
*
|
|
37112
|
+
* const dist = await getMaxDrawdownDistancePnlCost("BTCUSDT");
|
|
37113
|
+
* // e.g. 7.2 (peak was $7.2 above trough)
|
|
37114
|
+
* ```
|
|
37115
|
+
*/
|
|
37116
|
+
async function getMaxDrawdownDistancePnlCost(symbol) {
|
|
37117
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
37118
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37119
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires an execution context");
|
|
37120
|
+
}
|
|
37121
|
+
if (!MethodContextService.hasContext()) {
|
|
37122
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires a method context");
|
|
37123
|
+
}
|
|
37124
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37125
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37126
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37127
|
+
}
|
|
36914
37128
|
/**
|
|
36915
37129
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36916
37130
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38584,6 +38798,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE =
|
|
|
38584
38798
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38585
38799
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38586
38800
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38801
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getMaxDrawdownDistancePnlPercentage";
|
|
38802
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "BacktestUtils.getMaxDrawdownDistancePnlCost";
|
|
38587
38803
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38588
38804
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38589
38805
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39965,6 +40181,62 @@ class BacktestUtils {
|
|
|
39965
40181
|
}
|
|
39966
40182
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39967
40183
|
};
|
|
40184
|
+
/**
|
|
40185
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
40186
|
+
*
|
|
40187
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
40188
|
+
* Returns null if no pending signal exists.
|
|
40189
|
+
*
|
|
40190
|
+
* @param symbol - Trading pair symbol
|
|
40191
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40192
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
40193
|
+
*/
|
|
40194
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
40195
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
40196
|
+
symbol,
|
|
40197
|
+
context,
|
|
40198
|
+
});
|
|
40199
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40200
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40201
|
+
{
|
|
40202
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40203
|
+
riskName &&
|
|
40204
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40205
|
+
riskList &&
|
|
40206
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40207
|
+
actions &&
|
|
40208
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40209
|
+
}
|
|
40210
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(true, symbol, context);
|
|
40211
|
+
};
|
|
40212
|
+
/**
|
|
40213
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
40214
|
+
*
|
|
40215
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
40216
|
+
* Returns null if no pending signal exists.
|
|
40217
|
+
*
|
|
40218
|
+
* @param symbol - Trading pair symbol
|
|
40219
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40220
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
40221
|
+
*/
|
|
40222
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
40223
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
40224
|
+
symbol,
|
|
40225
|
+
context,
|
|
40226
|
+
});
|
|
40227
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40228
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40229
|
+
{
|
|
40230
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40231
|
+
riskName &&
|
|
40232
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40233
|
+
riskList &&
|
|
40234
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40235
|
+
actions &&
|
|
40236
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40237
|
+
}
|
|
40238
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(true, symbol, context);
|
|
40239
|
+
};
|
|
39968
40240
|
/**
|
|
39969
40241
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39970
40242
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40099,7 +40371,7 @@ class BacktestUtils {
|
|
|
40099
40371
|
* @param symbol - Trading pair symbol
|
|
40100
40372
|
* @param strategyName - Strategy name
|
|
40101
40373
|
* @param context - Execution context with exchangeName and frameName
|
|
40102
|
-
* @param
|
|
40374
|
+
* @param payload - Optional commit payload with id and note
|
|
40103
40375
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
40104
40376
|
*
|
|
40105
40377
|
* @example
|
|
@@ -40109,14 +40381,14 @@ class BacktestUtils {
|
|
|
40109
40381
|
* exchangeName: "binance",
|
|
40110
40382
|
* frameName: "frame1",
|
|
40111
40383
|
* strategyName: "my-strategy"
|
|
40112
|
-
* }, "manual-cancel-001");
|
|
40384
|
+
* }, { id: "manual-cancel-001" });
|
|
40113
40385
|
* ```
|
|
40114
40386
|
*/
|
|
40115
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
40387
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
40116
40388
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
40117
40389
|
symbol,
|
|
40118
40390
|
context,
|
|
40119
|
-
|
|
40391
|
+
payload,
|
|
40120
40392
|
});
|
|
40121
40393
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
40122
40394
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -40129,7 +40401,7 @@ class BacktestUtils {
|
|
|
40129
40401
|
actions &&
|
|
40130
40402
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED));
|
|
40131
40403
|
}
|
|
40132
|
-
await backtest.strategyCoreService.cancelScheduled(true, symbol, context,
|
|
40404
|
+
await backtest.strategyCoreService.cancelScheduled(true, symbol, context, payload);
|
|
40133
40405
|
};
|
|
40134
40406
|
/**
|
|
40135
40407
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -40140,7 +40412,7 @@ class BacktestUtils {
|
|
|
40140
40412
|
*
|
|
40141
40413
|
* @param symbol - Trading pair symbol
|
|
40142
40414
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40143
|
-
* @param
|
|
40415
|
+
* @param payload - Optional commit payload with id and note
|
|
40144
40416
|
* @returns Promise that resolves when pending signal is closed
|
|
40145
40417
|
*
|
|
40146
40418
|
* @example
|
|
@@ -40150,14 +40422,14 @@ class BacktestUtils {
|
|
|
40150
40422
|
* exchangeName: "binance",
|
|
40151
40423
|
* strategyName: "my-strategy",
|
|
40152
40424
|
* frameName: "1m"
|
|
40153
|
-
* }, "manual-close-001");
|
|
40425
|
+
* }, { id: "manual-close-001" });
|
|
40154
40426
|
* ```
|
|
40155
40427
|
*/
|
|
40156
|
-
this.commitClosePending = async (symbol, context,
|
|
40428
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
40157
40429
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CLOSE_PENDING, {
|
|
40158
40430
|
symbol,
|
|
40159
40431
|
context,
|
|
40160
|
-
|
|
40432
|
+
payload,
|
|
40161
40433
|
});
|
|
40162
40434
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
40163
40435
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -40170,7 +40442,7 @@ class BacktestUtils {
|
|
|
40170
40442
|
actions &&
|
|
40171
40443
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CLOSE_PENDING));
|
|
40172
40444
|
}
|
|
40173
|
-
await backtest.strategyCoreService.closePending(true, symbol, context,
|
|
40445
|
+
await backtest.strategyCoreService.closePending(true, symbol, context, payload);
|
|
40174
40446
|
};
|
|
40175
40447
|
/**
|
|
40176
40448
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -40806,7 +41078,7 @@ class BacktestUtils {
|
|
|
40806
41078
|
*
|
|
40807
41079
|
* @param symbol - Trading pair symbol
|
|
40808
41080
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40809
|
-
* @param
|
|
41081
|
+
* @param payload - Optional commit payload with id and note
|
|
40810
41082
|
* @returns Promise that resolves when activation flag is set
|
|
40811
41083
|
*
|
|
40812
41084
|
* @example
|
|
@@ -40816,14 +41088,14 @@ class BacktestUtils {
|
|
|
40816
41088
|
* strategyName: "my-strategy",
|
|
40817
41089
|
* exchangeName: "binance",
|
|
40818
41090
|
* frameName: "1h"
|
|
40819
|
-
* }, "manual-activate-001");
|
|
41091
|
+
* }, { id: "manual-activate-001" });
|
|
40820
41092
|
* ```
|
|
40821
41093
|
*/
|
|
40822
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
41094
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
40823
41095
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
40824
41096
|
symbol,
|
|
40825
41097
|
context,
|
|
40826
|
-
|
|
41098
|
+
payload,
|
|
40827
41099
|
});
|
|
40828
41100
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
40829
41101
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -40836,7 +41108,7 @@ class BacktestUtils {
|
|
|
40836
41108
|
actions &&
|
|
40837
41109
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
40838
41110
|
}
|
|
40839
|
-
await backtest.strategyCoreService.activateScheduled(true, symbol, context,
|
|
41111
|
+
await backtest.strategyCoreService.activateScheduled(true, symbol, context, payload);
|
|
40840
41112
|
};
|
|
40841
41113
|
/**
|
|
40842
41114
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -41094,6 +41366,8 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "Li
|
|
|
41094
41366
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41095
41367
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41096
41368
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
41369
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getMaxDrawdownDistancePnlPercentage";
|
|
41370
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "LiveUtils.getMaxDrawdownDistancePnlCost";
|
|
41097
41371
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
41098
41372
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
41099
41373
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -42618,6 +42892,70 @@ class LiveUtils {
|
|
|
42618
42892
|
frameName: "",
|
|
42619
42893
|
});
|
|
42620
42894
|
};
|
|
42895
|
+
/**
|
|
42896
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
42897
|
+
*
|
|
42898
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
42899
|
+
* Returns null if no pending signal exists.
|
|
42900
|
+
*
|
|
42901
|
+
* @param symbol - Trading pair symbol
|
|
42902
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42903
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
42904
|
+
*/
|
|
42905
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
42906
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
42907
|
+
symbol,
|
|
42908
|
+
context,
|
|
42909
|
+
});
|
|
42910
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
42911
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
42912
|
+
{
|
|
42913
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42914
|
+
riskName &&
|
|
42915
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
42916
|
+
riskList &&
|
|
42917
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
42918
|
+
actions &&
|
|
42919
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
42920
|
+
}
|
|
42921
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(false, symbol, {
|
|
42922
|
+
strategyName: context.strategyName,
|
|
42923
|
+
exchangeName: context.exchangeName,
|
|
42924
|
+
frameName: "",
|
|
42925
|
+
});
|
|
42926
|
+
};
|
|
42927
|
+
/**
|
|
42928
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
42929
|
+
*
|
|
42930
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
42931
|
+
* Returns null if no pending signal exists.
|
|
42932
|
+
*
|
|
42933
|
+
* @param symbol - Trading pair symbol
|
|
42934
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42935
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
42936
|
+
*/
|
|
42937
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
42938
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
42939
|
+
symbol,
|
|
42940
|
+
context,
|
|
42941
|
+
});
|
|
42942
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
42943
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
42944
|
+
{
|
|
42945
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42946
|
+
riskName &&
|
|
42947
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
42948
|
+
riskList &&
|
|
42949
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
42950
|
+
actions &&
|
|
42951
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
42952
|
+
}
|
|
42953
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(false, symbol, {
|
|
42954
|
+
strategyName: context.strategyName,
|
|
42955
|
+
exchangeName: context.exchangeName,
|
|
42956
|
+
frameName: "",
|
|
42957
|
+
});
|
|
42958
|
+
};
|
|
42621
42959
|
/**
|
|
42622
42960
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
42623
42961
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -42759,7 +43097,7 @@ class LiveUtils {
|
|
|
42759
43097
|
* @param symbol - Trading pair symbol
|
|
42760
43098
|
* @param strategyName - Strategy name
|
|
42761
43099
|
* @param context - Execution context with exchangeName and frameName
|
|
42762
|
-
* @param
|
|
43100
|
+
* @param payload - Optional commit payload with id and note
|
|
42763
43101
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
42764
43102
|
*
|
|
42765
43103
|
* @example
|
|
@@ -42769,14 +43107,14 @@ class LiveUtils {
|
|
|
42769
43107
|
* exchangeName: "binance",
|
|
42770
43108
|
* frameName: "",
|
|
42771
43109
|
* strategyName: "my-strategy"
|
|
42772
|
-
* }, "manual-cancel-001");
|
|
43110
|
+
* }, { id: "manual-cancel-001" });
|
|
42773
43111
|
* ```
|
|
42774
43112
|
*/
|
|
42775
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
43113
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
42776
43114
|
backtest.loggerService.info(LIVE_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
42777
43115
|
symbol,
|
|
42778
43116
|
context,
|
|
42779
|
-
|
|
43117
|
+
payload,
|
|
42780
43118
|
});
|
|
42781
43119
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
42782
43120
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -42793,7 +43131,7 @@ class LiveUtils {
|
|
|
42793
43131
|
strategyName: context.strategyName,
|
|
42794
43132
|
exchangeName: context.exchangeName,
|
|
42795
43133
|
frameName: "",
|
|
42796
|
-
},
|
|
43134
|
+
}, payload);
|
|
42797
43135
|
};
|
|
42798
43136
|
/**
|
|
42799
43137
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -42804,7 +43142,7 @@ class LiveUtils {
|
|
|
42804
43142
|
*
|
|
42805
43143
|
* @param symbol - Trading pair symbol
|
|
42806
43144
|
* @param context - Execution context with strategyName and exchangeName
|
|
42807
|
-
* @param
|
|
43145
|
+
* @param payload - Optional commit payload with id and note
|
|
42808
43146
|
* @returns Promise that resolves when pending signal is closed
|
|
42809
43147
|
*
|
|
42810
43148
|
* @example
|
|
@@ -42813,14 +43151,14 @@ class LiveUtils {
|
|
|
42813
43151
|
* await Live.commitClose("BTCUSDT", {
|
|
42814
43152
|
* exchangeName: "binance",
|
|
42815
43153
|
* strategyName: "my-strategy"
|
|
42816
|
-
* }, "manual-close-001");
|
|
43154
|
+
* }, { id: "manual-close-001" });
|
|
42817
43155
|
* ```
|
|
42818
43156
|
*/
|
|
42819
|
-
this.commitClosePending = async (symbol, context,
|
|
43157
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
42820
43158
|
backtest.loggerService.info(LIVE_METHOD_NAME_CLOSE_PENDING, {
|
|
42821
43159
|
symbol,
|
|
42822
43160
|
context,
|
|
42823
|
-
|
|
43161
|
+
payload,
|
|
42824
43162
|
});
|
|
42825
43163
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
42826
43164
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -42837,7 +43175,7 @@ class LiveUtils {
|
|
|
42837
43175
|
strategyName: context.strategyName,
|
|
42838
43176
|
exchangeName: context.exchangeName,
|
|
42839
43177
|
frameName: "",
|
|
42840
|
-
},
|
|
43178
|
+
}, payload);
|
|
42841
43179
|
};
|
|
42842
43180
|
/**
|
|
42843
43181
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -43631,7 +43969,7 @@ class LiveUtils {
|
|
|
43631
43969
|
*
|
|
43632
43970
|
* @param symbol - Trading pair symbol
|
|
43633
43971
|
* @param context - Execution context with strategyName and exchangeName
|
|
43634
|
-
* @param
|
|
43972
|
+
* @param payload - Optional commit payload with id and note
|
|
43635
43973
|
* @returns Promise that resolves when activation flag is set
|
|
43636
43974
|
*
|
|
43637
43975
|
* @example
|
|
@@ -43640,14 +43978,14 @@ class LiveUtils {
|
|
|
43640
43978
|
* await Live.commitActivateScheduled("BTCUSDT", {
|
|
43641
43979
|
* strategyName: "my-strategy",
|
|
43642
43980
|
* exchangeName: "binance"
|
|
43643
|
-
* }, "manual-activate-001");
|
|
43981
|
+
* }, { id: "manual-activate-001" });
|
|
43644
43982
|
* ```
|
|
43645
43983
|
*/
|
|
43646
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
43984
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
43647
43985
|
backtest.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
43648
43986
|
symbol,
|
|
43649
43987
|
context,
|
|
43650
|
-
|
|
43988
|
+
payload,
|
|
43651
43989
|
});
|
|
43652
43990
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
43653
43991
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -43664,7 +44002,7 @@ class LiveUtils {
|
|
|
43664
44002
|
strategyName: context.strategyName,
|
|
43665
44003
|
exchangeName: context.exchangeName,
|
|
43666
44004
|
frameName: "",
|
|
43667
|
-
},
|
|
44005
|
+
}, payload);
|
|
43668
44006
|
};
|
|
43669
44007
|
/**
|
|
43670
44008
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -49735,6 +50073,741 @@ class MaxDrawdownUtils {
|
|
|
49735
50073
|
*/
|
|
49736
50074
|
const MaxDrawdown = new MaxDrawdownUtils();
|
|
49737
50075
|
|
|
50076
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT = "ReflectUtils.getPositionPnlPercent";
|
|
50077
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_COST = "ReflectUtils.getPositionPnlCost";
|
|
50078
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "ReflectUtils.getPositionHighestProfitPrice";
|
|
50079
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.getPositionHighestProfitTimestamp";
|
|
50080
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
|
|
50081
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
|
|
50082
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
|
|
50083
|
+
const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
|
|
50084
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
|
|
50085
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
|
|
50086
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "ReflectUtils.getPositionMaxDrawdownPrice";
|
|
50087
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "ReflectUtils.getPositionMaxDrawdownTimestamp";
|
|
50088
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionMaxDrawdownPnlPercentage";
|
|
50089
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionMaxDrawdownPnlCost";
|
|
50090
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
50091
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "ReflectUtils.getPositionHighestProfitDistancePnlCost";
|
|
50092
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
50093
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
50094
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getMaxDrawdownDistancePnlPercentage";
|
|
50095
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "ReflectUtils.getMaxDrawdownDistancePnlCost";
|
|
50096
|
+
/**
|
|
50097
|
+
* Utility class for real-time position reflection: PNL, peak profit, and drawdown queries.
|
|
50098
|
+
*
|
|
50099
|
+
* Provides unified access to strategyCoreService position state methods with logging
|
|
50100
|
+
* and full validation (strategy, exchange, frame, risk, actions).
|
|
50101
|
+
* Works for both live and backtest modes via the `backtest` parameter.
|
|
50102
|
+
* Exported as singleton instance for convenient usage.
|
|
50103
|
+
*
|
|
50104
|
+
* @example
|
|
50105
|
+
* ```typescript
|
|
50106
|
+
* import { Reflect } from "backtest-kit";
|
|
50107
|
+
*
|
|
50108
|
+
* // Get current unrealized PNL percentage
|
|
50109
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50110
|
+
* "BTCUSDT",
|
|
50111
|
+
* 45000,
|
|
50112
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50113
|
+
* );
|
|
50114
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50115
|
+
*
|
|
50116
|
+
* // Get peak profit reached
|
|
50117
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50118
|
+
* "BTCUSDT",
|
|
50119
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50120
|
+
* );
|
|
50121
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50122
|
+
* ```
|
|
50123
|
+
*/
|
|
50124
|
+
class ReflectUtils {
|
|
50125
|
+
constructor() {
|
|
50126
|
+
/**
|
|
50127
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
50128
|
+
*
|
|
50129
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50130
|
+
* Returns null if no pending signal exists.
|
|
50131
|
+
*
|
|
50132
|
+
* @param symbol - Trading pair symbol
|
|
50133
|
+
* @param currentPrice - Current market price
|
|
50134
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50135
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50136
|
+
* @returns Promise resolving to PNL percentage or null
|
|
50137
|
+
*
|
|
50138
|
+
* @example
|
|
50139
|
+
* ```typescript
|
|
50140
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50141
|
+
* "BTCUSDT",
|
|
50142
|
+
* 45000,
|
|
50143
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50144
|
+
* );
|
|
50145
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50146
|
+
* ```
|
|
50147
|
+
*/
|
|
50148
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50149
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
|
|
50150
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50151
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50152
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50153
|
+
{
|
|
50154
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50155
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50156
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50157
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50158
|
+
}
|
|
50159
|
+
return await backtest.strategyCoreService.getPositionPnlPercent(backtest$1, symbol, currentPrice, context);
|
|
50160
|
+
};
|
|
50161
|
+
/**
|
|
50162
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
50163
|
+
*
|
|
50164
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost.
|
|
50165
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50166
|
+
* Returns null if no pending signal exists.
|
|
50167
|
+
*
|
|
50168
|
+
* @param symbol - Trading pair symbol
|
|
50169
|
+
* @param currentPrice - Current market price
|
|
50170
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50171
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50172
|
+
* @returns Promise resolving to PNL in dollars or null
|
|
50173
|
+
*
|
|
50174
|
+
* @example
|
|
50175
|
+
* ```typescript
|
|
50176
|
+
* const pnlCost = await Reflect.getPositionPnlCost(
|
|
50177
|
+
* "BTCUSDT",
|
|
50178
|
+
* 45000,
|
|
50179
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50180
|
+
* );
|
|
50181
|
+
* console.log(`PNL: $${pnlCost}`);
|
|
50182
|
+
* ```
|
|
50183
|
+
*/
|
|
50184
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50185
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
|
|
50186
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50187
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50188
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50189
|
+
{
|
|
50190
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50191
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50192
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50193
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50194
|
+
}
|
|
50195
|
+
return await backtest.strategyCoreService.getPositionPnlCost(backtest$1, symbol, currentPrice, context);
|
|
50196
|
+
};
|
|
50197
|
+
/**
|
|
50198
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
50199
|
+
*
|
|
50200
|
+
* Returns null if no pending signal exists.
|
|
50201
|
+
*
|
|
50202
|
+
* @param symbol - Trading pair symbol
|
|
50203
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50204
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50205
|
+
* @returns Promise resolving to price or null
|
|
50206
|
+
*
|
|
50207
|
+
* @example
|
|
50208
|
+
* ```typescript
|
|
50209
|
+
* const peakPrice = await Reflect.getPositionHighestProfitPrice(
|
|
50210
|
+
* "BTCUSDT",
|
|
50211
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50212
|
+
* );
|
|
50213
|
+
* console.log(`Peak price: ${peakPrice}`);
|
|
50214
|
+
* ```
|
|
50215
|
+
*/
|
|
50216
|
+
this.getPositionHighestProfitPrice = async (symbol, context, backtest$1 = false) => {
|
|
50217
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, { symbol, context });
|
|
50218
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50219
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50220
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50221
|
+
{
|
|
50222
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50223
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50224
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50225
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50226
|
+
}
|
|
50227
|
+
return await backtest.strategyCoreService.getPositionHighestProfitPrice(backtest$1, symbol, context);
|
|
50228
|
+
};
|
|
50229
|
+
/**
|
|
50230
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
50231
|
+
*
|
|
50232
|
+
* Returns null if no pending signal exists.
|
|
50233
|
+
*
|
|
50234
|
+
* @param symbol - Trading pair symbol
|
|
50235
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50236
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50237
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
50238
|
+
*
|
|
50239
|
+
* @example
|
|
50240
|
+
* ```typescript
|
|
50241
|
+
* const ts = await Reflect.getPositionHighestProfitTimestamp(
|
|
50242
|
+
* "BTCUSDT",
|
|
50243
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50244
|
+
* );
|
|
50245
|
+
* console.log(`Peak at: ${new Date(ts).toISOString()}`);
|
|
50246
|
+
* ```
|
|
50247
|
+
*/
|
|
50248
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
50249
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, { symbol, context });
|
|
50250
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50251
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50252
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50253
|
+
{
|
|
50254
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50255
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50256
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50257
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50258
|
+
}
|
|
50259
|
+
return await backtest.strategyCoreService.getPositionHighestProfitTimestamp(backtest$1, symbol, context);
|
|
50260
|
+
};
|
|
50261
|
+
/**
|
|
50262
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
50263
|
+
*
|
|
50264
|
+
* Returns null if no pending signal exists.
|
|
50265
|
+
*
|
|
50266
|
+
* @param symbol - Trading pair symbol
|
|
50267
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50268
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50269
|
+
* @returns Promise resolving to PnL percentage or null
|
|
50270
|
+
*
|
|
50271
|
+
* @example
|
|
50272
|
+
* ```typescript
|
|
50273
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50274
|
+
* "BTCUSDT",
|
|
50275
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50276
|
+
* );
|
|
50277
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50278
|
+
* ```
|
|
50279
|
+
*/
|
|
50280
|
+
this.getPositionHighestPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50281
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, { symbol, context });
|
|
50282
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50283
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50284
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50285
|
+
{
|
|
50286
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50287
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50288
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50289
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50290
|
+
}
|
|
50291
|
+
return await backtest.strategyCoreService.getPositionHighestPnlPercentage(backtest$1, symbol, context);
|
|
50292
|
+
};
|
|
50293
|
+
/**
|
|
50294
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
50295
|
+
*
|
|
50296
|
+
* Returns null if no pending signal exists.
|
|
50297
|
+
*
|
|
50298
|
+
* @param symbol - Trading pair symbol
|
|
50299
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50300
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50301
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
50302
|
+
*
|
|
50303
|
+
* @example
|
|
50304
|
+
* ```typescript
|
|
50305
|
+
* const peakCost = await Reflect.getPositionHighestPnlCost(
|
|
50306
|
+
* "BTCUSDT",
|
|
50307
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50308
|
+
* );
|
|
50309
|
+
* console.log(`Peak PNL: $${peakCost}`);
|
|
50310
|
+
* ```
|
|
50311
|
+
*/
|
|
50312
|
+
this.getPositionHighestPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50313
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, { symbol, context });
|
|
50314
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50315
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50316
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50317
|
+
{
|
|
50318
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50319
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50320
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50321
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50322
|
+
}
|
|
50323
|
+
return await backtest.strategyCoreService.getPositionHighestPnlCost(backtest$1, symbol, context);
|
|
50324
|
+
};
|
|
50325
|
+
/**
|
|
50326
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
50327
|
+
*
|
|
50328
|
+
* Returns null if no pending signal exists.
|
|
50329
|
+
*
|
|
50330
|
+
* @param symbol - Trading pair symbol
|
|
50331
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50332
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50333
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
50334
|
+
*
|
|
50335
|
+
* @example
|
|
50336
|
+
* ```typescript
|
|
50337
|
+
* const wasReachable = await Reflect.getPositionHighestProfitBreakeven(
|
|
50338
|
+
* "BTCUSDT",
|
|
50339
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50340
|
+
* );
|
|
50341
|
+
* console.log(`Breakeven reachable at peak: ${wasReachable}`);
|
|
50342
|
+
* ```
|
|
50343
|
+
*/
|
|
50344
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context, backtest$1 = false) => {
|
|
50345
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, { symbol, context });
|
|
50346
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50347
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50348
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50349
|
+
{
|
|
50350
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50351
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
50352
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
50353
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
50354
|
+
}
|
|
50355
|
+
return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
|
|
50356
|
+
};
|
|
50357
|
+
/**
|
|
50358
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
50359
|
+
*
|
|
50360
|
+
* Returns null if no pending signal exists.
|
|
50361
|
+
*
|
|
50362
|
+
* @param symbol - Trading pair symbol
|
|
50363
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50364
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50365
|
+
* @returns Promise resolving to minutes since highest profit price was recorded, or null
|
|
50366
|
+
*
|
|
50367
|
+
* @example
|
|
50368
|
+
* ```typescript
|
|
50369
|
+
* const minutes = await Reflect.getPositionDrawdownMinutes(
|
|
50370
|
+
* "BTCUSDT",
|
|
50371
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50372
|
+
* );
|
|
50373
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
50374
|
+
* ```
|
|
50375
|
+
*/
|
|
50376
|
+
this.getPositionDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
50377
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, { symbol, context });
|
|
50378
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50379
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50380
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50381
|
+
{
|
|
50382
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50383
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
50384
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
50385
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
50386
|
+
}
|
|
50387
|
+
return await backtest.strategyCoreService.getPositionDrawdownMinutes(backtest$1, symbol, context);
|
|
50388
|
+
};
|
|
50389
|
+
/**
|
|
50390
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
50391
|
+
*
|
|
50392
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
50393
|
+
* pulling back from its peak profit level.
|
|
50394
|
+
* Returns null if no pending signal exists.
|
|
50395
|
+
*
|
|
50396
|
+
* @param symbol - Trading pair symbol
|
|
50397
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50398
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50399
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
50400
|
+
*
|
|
50401
|
+
* @example
|
|
50402
|
+
* ```typescript
|
|
50403
|
+
* const minutes = await Reflect.getPositionHighestProfitMinutes(
|
|
50404
|
+
* "BTCUSDT",
|
|
50405
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50406
|
+
* );
|
|
50407
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
50408
|
+
* ```
|
|
50409
|
+
*/
|
|
50410
|
+
this.getPositionHighestProfitMinutes = async (symbol, context, backtest$1 = false) => {
|
|
50411
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, { symbol, context });
|
|
50412
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50413
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50414
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50415
|
+
{
|
|
50416
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50417
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
50418
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
50419
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
50420
|
+
}
|
|
50421
|
+
return await backtest.strategyCoreService.getPositionHighestProfitMinutes(backtest$1, symbol, context);
|
|
50422
|
+
};
|
|
50423
|
+
/**
|
|
50424
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
50425
|
+
*
|
|
50426
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
50427
|
+
* Zero when called at the exact moment the trough was set.
|
|
50428
|
+
* Returns null if no pending signal exists.
|
|
50429
|
+
*
|
|
50430
|
+
* @param symbol - Trading pair symbol
|
|
50431
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50432
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50433
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
50434
|
+
*
|
|
50435
|
+
* @example
|
|
50436
|
+
* ```typescript
|
|
50437
|
+
* const minutes = await Reflect.getPositionMaxDrawdownMinutes(
|
|
50438
|
+
* "BTCUSDT",
|
|
50439
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50440
|
+
* );
|
|
50441
|
+
* console.log(`Drawdown trough was ${minutes} minutes ago`);
|
|
50442
|
+
* ```
|
|
50443
|
+
*/
|
|
50444
|
+
this.getPositionMaxDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
50445
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, { symbol, context });
|
|
50446
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50447
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50448
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50449
|
+
{
|
|
50450
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50451
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
50452
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
50453
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
50454
|
+
}
|
|
50455
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownMinutes(backtest$1, symbol, context);
|
|
50456
|
+
};
|
|
50457
|
+
/**
|
|
50458
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
50459
|
+
*
|
|
50460
|
+
* Returns null if no pending signal exists.
|
|
50461
|
+
*
|
|
50462
|
+
* @param symbol - Trading pair symbol
|
|
50463
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50464
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50465
|
+
* @returns Promise resolving to price or null
|
|
50466
|
+
*
|
|
50467
|
+
* @example
|
|
50468
|
+
* ```typescript
|
|
50469
|
+
* const troughPrice = await Reflect.getPositionMaxDrawdownPrice(
|
|
50470
|
+
* "BTCUSDT",
|
|
50471
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50472
|
+
* );
|
|
50473
|
+
* console.log(`Worst price: ${troughPrice}`);
|
|
50474
|
+
* ```
|
|
50475
|
+
*/
|
|
50476
|
+
this.getPositionMaxDrawdownPrice = async (symbol, context, backtest$1 = false) => {
|
|
50477
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, { symbol, context });
|
|
50478
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50479
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50480
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50481
|
+
{
|
|
50482
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50483
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
50484
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
50485
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
50486
|
+
}
|
|
50487
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPrice(backtest$1, symbol, context);
|
|
50488
|
+
};
|
|
50489
|
+
/**
|
|
50490
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
50491
|
+
*
|
|
50492
|
+
* Returns null if no pending signal exists.
|
|
50493
|
+
*
|
|
50494
|
+
* @param symbol - Trading pair symbol
|
|
50495
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50496
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50497
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
50498
|
+
*
|
|
50499
|
+
* @example
|
|
50500
|
+
* ```typescript
|
|
50501
|
+
* const ts = await Reflect.getPositionMaxDrawdownTimestamp(
|
|
50502
|
+
* "BTCUSDT",
|
|
50503
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50504
|
+
* );
|
|
50505
|
+
* console.log(`Worst drawdown at: ${new Date(ts).toISOString()}`);
|
|
50506
|
+
* ```
|
|
50507
|
+
*/
|
|
50508
|
+
this.getPositionMaxDrawdownTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
50509
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, { symbol, context });
|
|
50510
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50511
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50512
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50513
|
+
{
|
|
50514
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50515
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
50516
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
50517
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
50518
|
+
}
|
|
50519
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownTimestamp(backtest$1, symbol, context);
|
|
50520
|
+
};
|
|
50521
|
+
/**
|
|
50522
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
50523
|
+
*
|
|
50524
|
+
* Returns null if no pending signal exists.
|
|
50525
|
+
*
|
|
50526
|
+
* @param symbol - Trading pair symbol
|
|
50527
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50528
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50529
|
+
* @returns Promise resolving to PnL percentage or null
|
|
50530
|
+
*
|
|
50531
|
+
* @example
|
|
50532
|
+
* ```typescript
|
|
50533
|
+
* const worstPnl = await Reflect.getPositionMaxDrawdownPnlPercentage(
|
|
50534
|
+
* "BTCUSDT",
|
|
50535
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50536
|
+
* );
|
|
50537
|
+
* console.log(`Worst PNL: ${worstPnl}%`);
|
|
50538
|
+
* ```
|
|
50539
|
+
*/
|
|
50540
|
+
this.getPositionMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50541
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
50542
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50543
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50544
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50545
|
+
{
|
|
50546
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50547
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50548
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50549
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50550
|
+
}
|
|
50551
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
50552
|
+
};
|
|
50553
|
+
/**
|
|
50554
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
50555
|
+
*
|
|
50556
|
+
* Returns null if no pending signal exists.
|
|
50557
|
+
*
|
|
50558
|
+
* @param symbol - Trading pair symbol
|
|
50559
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50560
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50561
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
50562
|
+
*
|
|
50563
|
+
* @example
|
|
50564
|
+
* ```typescript
|
|
50565
|
+
* const worstCost = await Reflect.getPositionMaxDrawdownPnlCost(
|
|
50566
|
+
* "BTCUSDT",
|
|
50567
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50568
|
+
* );
|
|
50569
|
+
* console.log(`Worst PNL: $${worstCost}`);
|
|
50570
|
+
* ```
|
|
50571
|
+
*/
|
|
50572
|
+
this.getPositionMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50573
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
50574
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50575
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50576
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50577
|
+
{
|
|
50578
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50579
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
50580
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
50581
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
50582
|
+
}
|
|
50583
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
50584
|
+
};
|
|
50585
|
+
/**
|
|
50586
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
50587
|
+
*
|
|
50588
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50589
|
+
*
|
|
50590
|
+
* @param symbol - Trading pair symbol
|
|
50591
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50592
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50593
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
50594
|
+
*
|
|
50595
|
+
* @example
|
|
50596
|
+
* ```typescript
|
|
50597
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
50598
|
+
* "BTCUSDT",
|
|
50599
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50600
|
+
* );
|
|
50601
|
+
* console.log(`Dropped ${distance}% from peak`);
|
|
50602
|
+
* ```
|
|
50603
|
+
*/
|
|
50604
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50605
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
50606
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50607
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50608
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50609
|
+
{
|
|
50610
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50611
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
50612
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
50613
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
50614
|
+
}
|
|
50615
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(backtest$1, symbol, context);
|
|
50616
|
+
};
|
|
50617
|
+
/**
|
|
50618
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
50619
|
+
*
|
|
50620
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50621
|
+
*
|
|
50622
|
+
* @param symbol - Trading pair symbol
|
|
50623
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50624
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50625
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
50626
|
+
*
|
|
50627
|
+
* @example
|
|
50628
|
+
* ```typescript
|
|
50629
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlCost(
|
|
50630
|
+
* "BTCUSDT",
|
|
50631
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50632
|
+
* );
|
|
50633
|
+
* console.log(`Dropped $${distance} from peak`);
|
|
50634
|
+
* ```
|
|
50635
|
+
*/
|
|
50636
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50637
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, { symbol, context });
|
|
50638
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50639
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50640
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50641
|
+
{
|
|
50642
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50643
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
50644
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
50645
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
50646
|
+
}
|
|
50647
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(backtest$1, symbol, context);
|
|
50648
|
+
};
|
|
50649
|
+
/**
|
|
50650
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
50651
|
+
*
|
|
50652
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50653
|
+
*
|
|
50654
|
+
* @param symbol - Trading pair symbol
|
|
50655
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50656
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50657
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL% (≥ 0) or null
|
|
50658
|
+
*
|
|
50659
|
+
* @example
|
|
50660
|
+
* ```typescript
|
|
50661
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlPercentage(
|
|
50662
|
+
* "BTCUSDT",
|
|
50663
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50664
|
+
* );
|
|
50665
|
+
* console.log(`${distance}% above worst trough`);
|
|
50666
|
+
* ```
|
|
50667
|
+
*/
|
|
50668
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50669
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
50670
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50671
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50672
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50673
|
+
{
|
|
50674
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50675
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
50676
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50677
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
50678
|
+
}
|
|
50679
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
50680
|
+
};
|
|
50681
|
+
/**
|
|
50682
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
50683
|
+
*
|
|
50684
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50685
|
+
*
|
|
50686
|
+
* @param symbol - Trading pair symbol
|
|
50687
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50688
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50689
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL cost (≥ 0) or null
|
|
50690
|
+
*
|
|
50691
|
+
* @example
|
|
50692
|
+
* ```typescript
|
|
50693
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlCost(
|
|
50694
|
+
* "BTCUSDT",
|
|
50695
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50696
|
+
* );
|
|
50697
|
+
* console.log(`$${distance} above worst trough`);
|
|
50698
|
+
* ```
|
|
50699
|
+
*/
|
|
50700
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50701
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
50702
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50703
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50704
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50705
|
+
{
|
|
50706
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50707
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
50708
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
50709
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
50710
|
+
}
|
|
50711
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
50712
|
+
};
|
|
50713
|
+
/**
|
|
50714
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
50715
|
+
*
|
|
50716
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50717
|
+
*
|
|
50718
|
+
* @param symbol - Trading pair symbol
|
|
50719
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50720
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50721
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
50722
|
+
*
|
|
50723
|
+
* @example
|
|
50724
|
+
* ```typescript
|
|
50725
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlPercentage(
|
|
50726
|
+
* "BTCUSDT",
|
|
50727
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50728
|
+
* );
|
|
50729
|
+
* console.log(`Peak-to-trough: ${distance}%`);
|
|
50730
|
+
* ```
|
|
50731
|
+
*/
|
|
50732
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50733
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
50734
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50735
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50736
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50737
|
+
{
|
|
50738
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50739
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
50740
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
50741
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
50742
|
+
}
|
|
50743
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(backtest$1, symbol, context);
|
|
50744
|
+
};
|
|
50745
|
+
/**
|
|
50746
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
50747
|
+
*
|
|
50748
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
50749
|
+
*
|
|
50750
|
+
* @param symbol - Trading pair symbol
|
|
50751
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50752
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50753
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
50754
|
+
*
|
|
50755
|
+
* @example
|
|
50756
|
+
* ```typescript
|
|
50757
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlCost(
|
|
50758
|
+
* "BTCUSDT",
|
|
50759
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50760
|
+
* );
|
|
50761
|
+
* console.log(`Peak-to-trough: $${distance}`);
|
|
50762
|
+
* ```
|
|
50763
|
+
*/
|
|
50764
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50765
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, { symbol, context });
|
|
50766
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50767
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50768
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50769
|
+
{
|
|
50770
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50771
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
50772
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
50773
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
50774
|
+
}
|
|
50775
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(backtest$1, symbol, context);
|
|
50776
|
+
};
|
|
50777
|
+
}
|
|
50778
|
+
}
|
|
50779
|
+
/**
|
|
50780
|
+
* Singleton instance of ReflectUtils for convenient position state queries.
|
|
50781
|
+
*
|
|
50782
|
+
* @example
|
|
50783
|
+
* ```typescript
|
|
50784
|
+
* import { Reflect } from "backtest-kit";
|
|
50785
|
+
*
|
|
50786
|
+
* // Real-time PNL
|
|
50787
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50788
|
+
* "BTCUSDT",
|
|
50789
|
+
* 45000,
|
|
50790
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50791
|
+
* );
|
|
50792
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50793
|
+
*
|
|
50794
|
+
* // Peak profit
|
|
50795
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50796
|
+
* "BTCUSDT",
|
|
50797
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50798
|
+
* );
|
|
50799
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50800
|
+
*
|
|
50801
|
+
* // Drawdown from peak
|
|
50802
|
+
* const drawdown = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
50803
|
+
* "BTCUSDT",
|
|
50804
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50805
|
+
* );
|
|
50806
|
+
* console.log(`Dropped ${drawdown}% from peak`);
|
|
50807
|
+
* ```
|
|
50808
|
+
*/
|
|
50809
|
+
const Reflect$1 = new ReflectUtils();
|
|
50810
|
+
|
|
49738
50811
|
/**
|
|
49739
50812
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
49740
50813
|
*
|
|
@@ -51665,6 +52738,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
51665
52738
|
pnlEntries: data.signal.pnl.pnlEntries,
|
|
51666
52739
|
scheduledAt: data.signal.scheduledAt,
|
|
51667
52740
|
currentPrice: data.currentPrice,
|
|
52741
|
+
note: data.signal.note,
|
|
51668
52742
|
createdAt: data.createdAt,
|
|
51669
52743
|
};
|
|
51670
52744
|
}
|
|
@@ -51694,6 +52768,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
51694
52768
|
duration: durationMin,
|
|
51695
52769
|
scheduledAt: data.signal.scheduledAt,
|
|
51696
52770
|
pendingAt: data.signal.pendingAt,
|
|
52771
|
+
note: data.signal.note,
|
|
51697
52772
|
createdAt: data.createdAt,
|
|
51698
52773
|
};
|
|
51699
52774
|
}
|
|
@@ -51730,6 +52805,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
51730
52805
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51731
52806
|
pnlCost: data.data.pnl.pnlCost,
|
|
51732
52807
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
52808
|
+
note: data.data.note,
|
|
51733
52809
|
scheduledAt: data.data.scheduledAt,
|
|
51734
52810
|
pendingAt: data.data.pendingAt,
|
|
51735
52811
|
createdAt: data.timestamp,
|
|
@@ -51765,6 +52841,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
51765
52841
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51766
52842
|
pnlCost: data.data.pnl.pnlCost,
|
|
51767
52843
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
52844
|
+
note: data.data.note,
|
|
51768
52845
|
scheduledAt: data.data.scheduledAt,
|
|
51769
52846
|
pendingAt: data.data.pendingAt,
|
|
51770
52847
|
createdAt: data.timestamp,
|
|
@@ -51799,6 +52876,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
|
51799
52876
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51800
52877
|
pnlCost: data.data.pnl.pnlCost,
|
|
51801
52878
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
52879
|
+
note: data.data.note,
|
|
51802
52880
|
scheduledAt: data.data.scheduledAt,
|
|
51803
52881
|
pendingAt: data.data.pendingAt,
|
|
51804
52882
|
createdAt: data.timestamp,
|
|
@@ -51840,6 +52918,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51840
52918
|
pnlEntries: data.pnl.pnlEntries,
|
|
51841
52919
|
scheduledAt: data.scheduledAt,
|
|
51842
52920
|
pendingAt: data.pendingAt,
|
|
52921
|
+
note: data.note,
|
|
51843
52922
|
createdAt: data.timestamp,
|
|
51844
52923
|
};
|
|
51845
52924
|
}
|
|
@@ -51872,6 +52951,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51872
52951
|
pnlEntries: data.pnl.pnlEntries,
|
|
51873
52952
|
scheduledAt: data.scheduledAt,
|
|
51874
52953
|
pendingAt: data.pendingAt,
|
|
52954
|
+
note: data.note,
|
|
51875
52955
|
createdAt: data.timestamp,
|
|
51876
52956
|
};
|
|
51877
52957
|
}
|
|
@@ -51903,6 +52983,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51903
52983
|
pnlEntries: data.pnl.pnlEntries,
|
|
51904
52984
|
scheduledAt: data.scheduledAt,
|
|
51905
52985
|
pendingAt: data.pendingAt,
|
|
52986
|
+
note: data.note,
|
|
51906
52987
|
createdAt: data.timestamp,
|
|
51907
52988
|
};
|
|
51908
52989
|
}
|
|
@@ -51935,6 +53016,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51935
53016
|
pnlEntries: data.pnl.pnlEntries,
|
|
51936
53017
|
scheduledAt: data.scheduledAt,
|
|
51937
53018
|
pendingAt: data.pendingAt,
|
|
53019
|
+
note: data.note,
|
|
51938
53020
|
createdAt: data.timestamp,
|
|
51939
53021
|
};
|
|
51940
53022
|
}
|
|
@@ -51967,6 +53049,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51967
53049
|
pnlEntries: data.pnl.pnlEntries,
|
|
51968
53050
|
scheduledAt: data.scheduledAt,
|
|
51969
53051
|
pendingAt: data.pendingAt,
|
|
53052
|
+
note: data.note,
|
|
51970
53053
|
createdAt: data.timestamp,
|
|
51971
53054
|
};
|
|
51972
53055
|
}
|
|
@@ -51999,6 +53082,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51999
53082
|
pnlEntries: data.pnl.pnlEntries,
|
|
52000
53083
|
scheduledAt: data.scheduledAt,
|
|
52001
53084
|
pendingAt: data.pendingAt,
|
|
53085
|
+
note: data.note,
|
|
52002
53086
|
createdAt: data.timestamp,
|
|
52003
53087
|
};
|
|
52004
53088
|
}
|
|
@@ -52032,6 +53116,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
52032
53116
|
pnlEntries: data.pnl.pnlEntries,
|
|
52033
53117
|
scheduledAt: data.scheduledAt,
|
|
52034
53118
|
pendingAt: data.pendingAt,
|
|
53119
|
+
note: data.note,
|
|
52035
53120
|
createdAt: data.timestamp,
|
|
52036
53121
|
};
|
|
52037
53122
|
}
|
|
@@ -52055,6 +53140,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
52055
53140
|
pnlPriceClose: data.pnl.priceClose,
|
|
52056
53141
|
pnlCost: data.pnl.pnlCost,
|
|
52057
53142
|
pnlEntries: data.pnl.pnlEntries,
|
|
53143
|
+
note: data.note,
|
|
52058
53144
|
createdAt: data.timestamp,
|
|
52059
53145
|
};
|
|
52060
53146
|
}
|
|
@@ -52078,6 +53164,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
52078
53164
|
pnlPriceClose: data.pnl.priceClose,
|
|
52079
53165
|
pnlCost: data.pnl.pnlCost,
|
|
52080
53166
|
pnlEntries: data.pnl.pnlEntries,
|
|
53167
|
+
note: data.note,
|
|
52081
53168
|
createdAt: data.timestamp,
|
|
52082
53169
|
};
|
|
52083
53170
|
}
|
|
@@ -52119,6 +53206,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
52119
53206
|
totalPartials: data.totalPartials,
|
|
52120
53207
|
scheduledAt: data.scheduledAt,
|
|
52121
53208
|
pendingAt: data.pendingAt,
|
|
53209
|
+
note: data.signal.note,
|
|
52122
53210
|
createdAt: data.timestamp,
|
|
52123
53211
|
};
|
|
52124
53212
|
}
|
|
@@ -52151,6 +53239,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
52151
53239
|
scheduledAt: data.scheduledAt,
|
|
52152
53240
|
pendingAt: data.pendingAt,
|
|
52153
53241
|
closeReason: data.closeReason,
|
|
53242
|
+
note: data.signal.note,
|
|
52154
53243
|
createdAt: data.timestamp,
|
|
52155
53244
|
};
|
|
52156
53245
|
}
|
|
@@ -53650,6 +54739,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
|
|
|
53650
54739
|
const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
53651
54740
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
53652
54741
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
54742
|
+
const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
|
|
53653
54743
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
53654
54744
|
const MS_PER_MINUTE$1 = 60000;
|
|
53655
54745
|
const INTERVAL_MINUTES$1 = {
|
|
@@ -53901,7 +54991,7 @@ class CacheFileInstance {
|
|
|
53901
54991
|
/**
|
|
53902
54992
|
* Clears the index counter.
|
|
53903
54993
|
*/
|
|
53904
|
-
static
|
|
54994
|
+
static resetCounter() {
|
|
53905
54995
|
CacheFileInstance._indexCounter = 0;
|
|
53906
54996
|
}
|
|
53907
54997
|
/**
|
|
@@ -54156,11 +55246,17 @@ class CacheUtils {
|
|
|
54156
55246
|
*/
|
|
54157
55247
|
this.clear = () => {
|
|
54158
55248
|
backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
|
|
54159
|
-
|
|
54160
|
-
|
|
54161
|
-
|
|
54162
|
-
|
|
54163
|
-
|
|
55249
|
+
this._getFnInstance.clear();
|
|
55250
|
+
this._getFileInstance.clear();
|
|
55251
|
+
};
|
|
55252
|
+
/**
|
|
55253
|
+
* Resets the CacheFileInstance index counter to zero.
|
|
55254
|
+
* This is useful when process.cwd() changes between strategy iterations to ensure
|
|
55255
|
+
* that new CacheFileInstance objects start with index 0 and do not collide with old instances.
|
|
55256
|
+
*/
|
|
55257
|
+
this.resetCounter = () => {
|
|
55258
|
+
backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
|
|
55259
|
+
CacheFileInstance.resetCounter();
|
|
54164
55260
|
};
|
|
54165
55261
|
}
|
|
54166
55262
|
}
|
|
@@ -54184,10 +55280,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
|
54184
55280
|
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
54185
55281
|
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
54186
55282
|
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
55283
|
+
const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
|
|
54187
55284
|
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
54188
55285
|
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
54189
55286
|
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
54190
55287
|
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
55288
|
+
const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
|
|
54191
55289
|
const MS_PER_MINUTE = 60000;
|
|
54192
55290
|
const INTERVAL_MINUTES = {
|
|
54193
55291
|
"1m": 1,
|
|
@@ -54254,6 +55352,8 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
|
54254
55352
|
*
|
|
54255
55353
|
* State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
|
|
54256
55354
|
*
|
|
55355
|
+
* @template F - Concrete function type
|
|
55356
|
+
*
|
|
54257
55357
|
* @example
|
|
54258
55358
|
* ```typescript
|
|
54259
55359
|
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
@@ -54269,30 +55369,34 @@ class IntervalFnInstance {
|
|
|
54269
55369
|
*
|
|
54270
55370
|
* @param fn - Function to fire once per interval
|
|
54271
55371
|
* @param interval - Candle interval that controls the firing boundary
|
|
55372
|
+
* @param key - Optional key generator for argument-based state separation.
|
|
55373
|
+
* Default: `([symbol]) => symbol`
|
|
54272
55374
|
*/
|
|
54273
|
-
constructor(fn, interval) {
|
|
55375
|
+
constructor(fn, interval, key = ([symbol]) => symbol) {
|
|
54274
55376
|
this.fn = fn;
|
|
54275
55377
|
this.interval = interval;
|
|
54276
|
-
|
|
55378
|
+
this.key = key;
|
|
55379
|
+
/** Stores the last aligned timestamp per context+symbol+args key. */
|
|
54277
55380
|
this._stateMap = new Map();
|
|
54278
55381
|
/**
|
|
54279
55382
|
* Execute the signal function with once-per-interval enforcement.
|
|
54280
55383
|
*
|
|
54281
55384
|
* Algorithm:
|
|
54282
55385
|
* 1. Align the current execution context `when` to the interval boundary.
|
|
54283
|
-
* 2.
|
|
54284
|
-
* 3.
|
|
55386
|
+
* 2. Build state key from context + key generator result.
|
|
55387
|
+
* 3. If the stored aligned timestamp for this key equals the current one → return `null`.
|
|
55388
|
+
* 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
|
|
54285
55389
|
* the signal. If it returns `null`, leave state unchanged so the next call retries.
|
|
54286
55390
|
*
|
|
54287
55391
|
* Requires active method context and execution context.
|
|
54288
55392
|
*
|
|
54289
|
-
* @param
|
|
55393
|
+
* @param args - Arguments forwarded to the wrapped function
|
|
54290
55394
|
* @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
54291
55395
|
* within the same interval or when `fn` itself returned `null`
|
|
54292
55396
|
* @throws Error if method context, execution context, or interval is missing
|
|
54293
55397
|
*/
|
|
54294
|
-
this.run = async (
|
|
54295
|
-
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, {
|
|
55398
|
+
this.run = async (...args) => {
|
|
55399
|
+
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
|
|
54296
55400
|
const step = INTERVAL_MINUTES[this.interval];
|
|
54297
55401
|
{
|
|
54298
55402
|
if (!MethodContextService.hasContext()) {
|
|
@@ -54306,15 +55410,16 @@ class IntervalFnInstance {
|
|
|
54306
55410
|
}
|
|
54307
55411
|
}
|
|
54308
55412
|
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
54309
|
-
const key = `${contextKey}:${symbol}`;
|
|
54310
55413
|
const currentWhen = backtest.executionContextService.context.when;
|
|
54311
55414
|
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
54312
|
-
|
|
55415
|
+
const argKey = this.key(args);
|
|
55416
|
+
const stateKey = `${contextKey}:${argKey}`;
|
|
55417
|
+
if (this._stateMap.get(stateKey) === currentAligned) {
|
|
54313
55418
|
return null;
|
|
54314
55419
|
}
|
|
54315
|
-
const result = await this.fn(
|
|
55420
|
+
const result = await this.fn.apply(null, args);
|
|
54316
55421
|
if (result !== null) {
|
|
54317
|
-
this._stateMap.set(
|
|
55422
|
+
this._stateMap.set(stateKey, currentAligned);
|
|
54318
55423
|
}
|
|
54319
55424
|
return result;
|
|
54320
55425
|
};
|
|
@@ -54335,6 +55440,28 @@ class IntervalFnInstance {
|
|
|
54335
55440
|
}
|
|
54336
55441
|
}
|
|
54337
55442
|
};
|
|
55443
|
+
/**
|
|
55444
|
+
* Garbage collect expired state entries.
|
|
55445
|
+
*
|
|
55446
|
+
* Removes all entries whose aligned timestamp differs from the current interval boundary.
|
|
55447
|
+
* Call this periodically to free memory from stale state entries.
|
|
55448
|
+
*
|
|
55449
|
+
* Requires active execution context to get current time.
|
|
55450
|
+
*
|
|
55451
|
+
* @returns Number of entries removed
|
|
55452
|
+
*/
|
|
55453
|
+
this.gc = () => {
|
|
55454
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
55455
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
55456
|
+
let removed = 0;
|
|
55457
|
+
for (const [key, storedAligned] of this._stateMap.entries()) {
|
|
55458
|
+
if (storedAligned !== currentAligned) {
|
|
55459
|
+
this._stateMap.delete(key);
|
|
55460
|
+
removed++;
|
|
55461
|
+
}
|
|
55462
|
+
}
|
|
55463
|
+
return removed;
|
|
55464
|
+
};
|
|
54338
55465
|
}
|
|
54339
55466
|
}
|
|
54340
55467
|
/**
|
|
@@ -54348,7 +55475,7 @@ class IntervalFnInstance {
|
|
|
54348
55475
|
*
|
|
54349
55476
|
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
54350
55477
|
*
|
|
54351
|
-
* @template
|
|
55478
|
+
* @template F - Concrete async function type
|
|
54352
55479
|
*
|
|
54353
55480
|
* @example
|
|
54354
55481
|
* ```typescript
|
|
@@ -54369,7 +55496,7 @@ class IntervalFileInstance {
|
|
|
54369
55496
|
* Resets the index counter to zero.
|
|
54370
55497
|
* Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
|
|
54371
55498
|
*/
|
|
54372
|
-
static
|
|
55499
|
+
static resetCounter() {
|
|
54373
55500
|
IntervalFileInstance._indexCounter = 0;
|
|
54374
55501
|
}
|
|
54375
55502
|
/**
|
|
@@ -54378,18 +55505,21 @@ class IntervalFileInstance {
|
|
|
54378
55505
|
* @param fn - Async signal function to fire once per interval
|
|
54379
55506
|
* @param interval - Candle interval that controls the firing boundary
|
|
54380
55507
|
* @param name - Human-readable bucket name used as the directory prefix
|
|
55508
|
+
* @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
|
|
55509
|
+
* Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
|
|
54381
55510
|
*/
|
|
54382
|
-
constructor(fn, interval, name) {
|
|
55511
|
+
constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
|
|
54383
55512
|
this.fn = fn;
|
|
54384
55513
|
this.interval = interval;
|
|
54385
55514
|
this.name = name;
|
|
55515
|
+
this.key = key;
|
|
54386
55516
|
/**
|
|
54387
55517
|
* Execute the async function with persistent once-per-interval enforcement.
|
|
54388
55518
|
*
|
|
54389
55519
|
* Algorithm:
|
|
54390
55520
|
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
54391
|
-
* 2. Align execution context `when` to interval boundary → `
|
|
54392
|
-
* 3. Build entity key
|
|
55521
|
+
* 2. Align execution context `when` to interval boundary → `alignedMs`.
|
|
55522
|
+
* 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
|
|
54393
55523
|
* 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
|
|
54394
55524
|
* 5. On hit — return `null` (interval already fired).
|
|
54395
55525
|
* 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
|
|
@@ -54397,12 +55527,13 @@ class IntervalFileInstance {
|
|
|
54397
55527
|
* Requires active method context and execution context.
|
|
54398
55528
|
*
|
|
54399
55529
|
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
55530
|
+
* @param args - Additional arguments forwarded to the wrapped function
|
|
54400
55531
|
* @returns The value on the first non-null fire, `null` if already fired this interval
|
|
54401
55532
|
* or if `fn` itself returned `null`
|
|
54402
55533
|
* @throws Error if method context, execution context, or interval is missing
|
|
54403
55534
|
*/
|
|
54404
|
-
this.run = async (
|
|
54405
|
-
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, {
|
|
55535
|
+
this.run = async (...args) => {
|
|
55536
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
|
|
54406
55537
|
const step = INTERVAL_MINUTES[this.interval];
|
|
54407
55538
|
{
|
|
54408
55539
|
if (!MethodContextService.hasContext()) {
|
|
@@ -54415,15 +55546,16 @@ class IntervalFileInstance {
|
|
|
54415
55546
|
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
54416
55547
|
}
|
|
54417
55548
|
}
|
|
55549
|
+
const [symbol, ...rest] = args;
|
|
54418
55550
|
const { when } = backtest.executionContextService.context;
|
|
54419
|
-
const
|
|
55551
|
+
const alignedMs = align(when.getTime(), this.interval);
|
|
54420
55552
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
54421
|
-
const entityKey =
|
|
55553
|
+
const entityKey = this.key([symbol, alignedMs, ...rest]);
|
|
54422
55554
|
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
54423
55555
|
if (cached !== null) {
|
|
54424
55556
|
return null;
|
|
54425
55557
|
}
|
|
54426
|
-
const result = await this.fn(
|
|
55558
|
+
const result = await this.fn.call(null, ...args);
|
|
54427
55559
|
if (result !== null) {
|
|
54428
55560
|
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
54429
55561
|
}
|
|
@@ -54464,12 +55596,12 @@ class IntervalUtils {
|
|
|
54464
55596
|
* Memoized factory to get or create an `IntervalFnInstance` for a function.
|
|
54465
55597
|
* Each function reference gets its own isolated instance.
|
|
54466
55598
|
*/
|
|
54467
|
-
this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
|
|
55599
|
+
this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
|
|
54468
55600
|
/**
|
|
54469
55601
|
* Memoized factory to get or create an `IntervalFileInstance` for an async function.
|
|
54470
55602
|
* Each function reference gets its own isolated persistent instance.
|
|
54471
55603
|
*/
|
|
54472
|
-
this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
|
|
55604
|
+
this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
|
|
54473
55605
|
/**
|
|
54474
55606
|
* Wrap a signal function with in-memory once-per-interval firing.
|
|
54475
55607
|
*
|
|
@@ -54481,21 +55613,30 @@ class IntervalUtils {
|
|
|
54481
55613
|
*
|
|
54482
55614
|
* @param run - Signal function to wrap
|
|
54483
55615
|
* @param context.interval - Candle interval that controls the firing boundary
|
|
54484
|
-
* @
|
|
55616
|
+
* @param context.key - Optional key generator for argument-based state separation
|
|
55617
|
+
* @returns Wrapped function with the same signature as `F`, plus a `clear()` method
|
|
54485
55618
|
*
|
|
54486
55619
|
* @example
|
|
54487
55620
|
* ```typescript
|
|
55621
|
+
* // Without extra args
|
|
54488
55622
|
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
54489
|
-
*
|
|
54490
55623
|
* await fireOnce("BTCUSDT"); // → T or null (fn called)
|
|
54491
55624
|
* await fireOnce("BTCUSDT"); // → null (same interval, skipped)
|
|
55625
|
+
*
|
|
55626
|
+
* // With extra args and key
|
|
55627
|
+
* const fireOnce = Interval.fn(mySignalFn, {
|
|
55628
|
+
* interval: "15m",
|
|
55629
|
+
* key: ([symbol, period]) => `${symbol}_${period}`,
|
|
55630
|
+
* });
|
|
55631
|
+
* await fireOnce("BTCUSDT", 14); // → T or null
|
|
55632
|
+
* await fireOnce("BTCUSDT", 28); // → T or null (separate state)
|
|
54492
55633
|
* ```
|
|
54493
55634
|
*/
|
|
54494
55635
|
this.fn = (run, context) => {
|
|
54495
55636
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
54496
|
-
const wrappedFn = (
|
|
54497
|
-
const instance = this._getInstance(run, context.interval);
|
|
54498
|
-
return instance.run(
|
|
55637
|
+
const wrappedFn = (...args) => {
|
|
55638
|
+
const instance = this._getInstance(run, context.interval, context.key);
|
|
55639
|
+
return instance.run(...args);
|
|
54499
55640
|
};
|
|
54500
55641
|
wrappedFn.clear = () => {
|
|
54501
55642
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
@@ -54509,6 +55650,14 @@ class IntervalUtils {
|
|
|
54509
55650
|
}
|
|
54510
55651
|
this._getInstance.get(run)?.clear();
|
|
54511
55652
|
};
|
|
55653
|
+
wrappedFn.gc = () => {
|
|
55654
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
|
|
55655
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55656
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
|
|
55657
|
+
return;
|
|
55658
|
+
}
|
|
55659
|
+
return this._getInstance.get(run)?.gc();
|
|
55660
|
+
};
|
|
54512
55661
|
return wrappedFn;
|
|
54513
55662
|
};
|
|
54514
55663
|
/**
|
|
@@ -54521,28 +55670,33 @@ class IntervalUtils {
|
|
|
54521
55670
|
* The `run` function reference is used as the memoization key for the underlying
|
|
54522
55671
|
* `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
|
|
54523
55672
|
*
|
|
54524
|
-
* @template
|
|
55673
|
+
* @template F - Concrete async function type
|
|
54525
55674
|
* @param run - Async signal function to wrap with persistent once-per-interval firing
|
|
54526
55675
|
* @param context.interval - Candle interval that controls the firing boundary
|
|
54527
55676
|
* @param context.name - Human-readable bucket name; becomes the directory prefix
|
|
54528
|
-
* @
|
|
54529
|
-
*
|
|
55677
|
+
* @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
|
|
55678
|
+
* Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
|
|
55679
|
+
* @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
|
|
54530
55680
|
*
|
|
54531
55681
|
* @example
|
|
54532
55682
|
* ```typescript
|
|
54533
|
-
* const fetchSignal = async (symbol: string,
|
|
54534
|
-
* const fireOnce = Interval.file(fetchSignal, {
|
|
54535
|
-
*
|
|
55683
|
+
* const fetchSignal = async (symbol: string, period: number) => { ... };
|
|
55684
|
+
* const fireOnce = Interval.file(fetchSignal, {
|
|
55685
|
+
* interval: "1h",
|
|
55686
|
+
* name: "fetchSignal",
|
|
55687
|
+
* key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
|
|
55688
|
+
* });
|
|
55689
|
+
* await fireOnce("BTCUSDT", 14);
|
|
54536
55690
|
* ```
|
|
54537
55691
|
*/
|
|
54538
55692
|
this.file = (run, context) => {
|
|
54539
55693
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
|
|
54540
55694
|
{
|
|
54541
|
-
this._getFileInstance(run, context.interval, context.name);
|
|
55695
|
+
this._getFileInstance(run, context.interval, context.name, context.key);
|
|
54542
55696
|
}
|
|
54543
|
-
const wrappedFn = (
|
|
54544
|
-
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
54545
|
-
return instance.run(
|
|
55697
|
+
const wrappedFn = (...args) => {
|
|
55698
|
+
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
55699
|
+
return instance.run(...args);
|
|
54546
55700
|
};
|
|
54547
55701
|
wrappedFn.clear = async () => {
|
|
54548
55702
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
@@ -54568,10 +55722,10 @@ class IntervalUtils {
|
|
|
54568
55722
|
this.dispose = (run) => {
|
|
54569
55723
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
|
|
54570
55724
|
this._getInstance.clear(run);
|
|
55725
|
+
this._getFileInstance.clear(run);
|
|
54571
55726
|
};
|
|
54572
55727
|
/**
|
|
54573
|
-
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects
|
|
54574
|
-
* resets the `IntervalFileInstance` index counter.
|
|
55728
|
+
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
|
|
54575
55729
|
* Call this when `process.cwd()` changes between strategy iterations
|
|
54576
55730
|
* so new instances are created with the updated base path.
|
|
54577
55731
|
*/
|
|
@@ -54579,7 +55733,15 @@ class IntervalUtils {
|
|
|
54579
55733
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
|
|
54580
55734
|
this._getInstance.clear();
|
|
54581
55735
|
this._getFileInstance.clear();
|
|
54582
|
-
|
|
55736
|
+
};
|
|
55737
|
+
/**
|
|
55738
|
+
* Resets the IntervalFileInstance index counter to zero.
|
|
55739
|
+
* This is useful when process.cwd() changes between strategy iterations to ensure
|
|
55740
|
+
* that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
|
|
55741
|
+
*/
|
|
55742
|
+
this.resetCounter = () => {
|
|
55743
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
|
|
55744
|
+
IntervalFileInstance.resetCounter();
|
|
54583
55745
|
};
|
|
54584
55746
|
}
|
|
54585
55747
|
}
|
|
@@ -55755,6 +56917,7 @@ exports.PersistSignalAdapter = PersistSignalAdapter;
|
|
|
55755
56917
|
exports.PersistStorageAdapter = PersistStorageAdapter;
|
|
55756
56918
|
exports.Position = Position;
|
|
55757
56919
|
exports.PositionSize = PositionSize;
|
|
56920
|
+
exports.Reflect = Reflect$1;
|
|
55758
56921
|
exports.Report = Report;
|
|
55759
56922
|
exports.ReportBase = ReportBase;
|
|
55760
56923
|
exports.ReportWriter = ReportWriter;
|
|
@@ -55813,6 +56976,8 @@ exports.getDefaultConfig = getDefaultConfig;
|
|
|
55813
56976
|
exports.getEffectivePriceOpen = getEffectivePriceOpen;
|
|
55814
56977
|
exports.getExchangeSchema = getExchangeSchema;
|
|
55815
56978
|
exports.getFrameSchema = getFrameSchema;
|
|
56979
|
+
exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
|
|
56980
|
+
exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
|
|
55816
56981
|
exports.getMode = getMode;
|
|
55817
56982
|
exports.getNextCandles = getNextCandles;
|
|
55818
56983
|
exports.getOrderBook = getOrderBook;
|