backtest-kit 6.9.0 → 6.11.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 +835 -111
- package/build/index.mjs +832 -112
- package/package.json +3 -3
- package/types.d.ts +478 -36
package/build/index.mjs
CHANGED
|
@@ -4127,53 +4127,6 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
4127
4127
|
};
|
|
4128
4128
|
};
|
|
4129
4129
|
|
|
4130
|
-
/**
|
|
4131
|
-
* Converts markdown content to plain text with minimal formatting
|
|
4132
|
-
* @param content - Markdown string to convert
|
|
4133
|
-
* @returns Plain text representation
|
|
4134
|
-
*/
|
|
4135
|
-
const toPlainString = (content) => {
|
|
4136
|
-
if (!content) {
|
|
4137
|
-
return "";
|
|
4138
|
-
}
|
|
4139
|
-
let text = content;
|
|
4140
|
-
// Remove code blocks
|
|
4141
|
-
text = text.replace(/```[\s\S]*?```/g, "");
|
|
4142
|
-
text = text.replace(/`([^`]+)`/g, "$1");
|
|
4143
|
-
// Remove images
|
|
4144
|
-
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
4145
|
-
// Convert links to text only (keep link text, remove URL)
|
|
4146
|
-
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
4147
|
-
// Remove headers (convert to plain text)
|
|
4148
|
-
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
4149
|
-
// Remove bold and italic markers
|
|
4150
|
-
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
4151
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
4152
|
-
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
4153
|
-
text = text.replace(/___(.+?)___/g, "$1");
|
|
4154
|
-
text = text.replace(/__(.+?)__/g, "$1");
|
|
4155
|
-
text = text.replace(/_(.+?)_/g, "$1");
|
|
4156
|
-
// Remove strikethrough
|
|
4157
|
-
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
4158
|
-
// Convert lists to plain text with bullets
|
|
4159
|
-
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
4160
|
-
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
4161
|
-
// Remove blockquotes
|
|
4162
|
-
text = text.replace(/^\s*>\s+/gm, "");
|
|
4163
|
-
// Remove horizontal rules
|
|
4164
|
-
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
4165
|
-
// Remove HTML tags
|
|
4166
|
-
text = text.replace(/<[^>]+>/g, "");
|
|
4167
|
-
// Remove excessive whitespace and normalize line breaks
|
|
4168
|
-
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
4169
|
-
text = text.replace(/[ \t]+/g, " ");
|
|
4170
|
-
// Remove all newline characters
|
|
4171
|
-
text = text.replace(/\n/g, " ");
|
|
4172
|
-
// Remove excessive spaces after newline removal
|
|
4173
|
-
text = text.replace(/\s+/g, " ");
|
|
4174
|
-
return text.trim();
|
|
4175
|
-
};
|
|
4176
|
-
|
|
4177
4130
|
/**
|
|
4178
4131
|
* Returns the total closed state of a position using costBasisAtClose snapshots.
|
|
4179
4132
|
*
|
|
@@ -4808,6 +4761,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4808
4761
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4809
4762
|
scheduledAt: publicSignal.scheduledAt,
|
|
4810
4763
|
pendingAt: publicSignal.pendingAt,
|
|
4764
|
+
note: publicSignal.note,
|
|
4811
4765
|
});
|
|
4812
4766
|
continue;
|
|
4813
4767
|
}
|
|
@@ -4835,6 +4789,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4835
4789
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4836
4790
|
scheduledAt: publicSignal.scheduledAt,
|
|
4837
4791
|
pendingAt: publicSignal.pendingAt,
|
|
4792
|
+
note: publicSignal.note,
|
|
4838
4793
|
});
|
|
4839
4794
|
continue;
|
|
4840
4795
|
}
|
|
@@ -4861,6 +4816,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4861
4816
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4862
4817
|
scheduledAt: publicSignal.scheduledAt,
|
|
4863
4818
|
pendingAt: publicSignal.pendingAt,
|
|
4819
|
+
note: publicSignal.note,
|
|
4864
4820
|
});
|
|
4865
4821
|
continue;
|
|
4866
4822
|
}
|
|
@@ -4888,6 +4844,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4888
4844
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4889
4845
|
scheduledAt: publicSignal.scheduledAt,
|
|
4890
4846
|
pendingAt: publicSignal.pendingAt,
|
|
4847
|
+
note: publicSignal.note,
|
|
4891
4848
|
});
|
|
4892
4849
|
continue;
|
|
4893
4850
|
}
|
|
@@ -4915,6 +4872,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4915
4872
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4916
4873
|
scheduledAt: publicSignal.scheduledAt,
|
|
4917
4874
|
pendingAt: publicSignal.pendingAt,
|
|
4875
|
+
note: publicSignal.note,
|
|
4918
4876
|
});
|
|
4919
4877
|
continue;
|
|
4920
4878
|
}
|
|
@@ -4944,6 +4902,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
|
|
|
4944
4902
|
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
4945
4903
|
scheduledAt: publicSignal.scheduledAt,
|
|
4946
4904
|
pendingAt: publicSignal.pendingAt,
|
|
4905
|
+
note: publicSignal.note,
|
|
4947
4906
|
});
|
|
4948
4907
|
continue;
|
|
4949
4908
|
}
|
|
@@ -5042,7 +5001,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5042
5001
|
const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
|
|
5043
5002
|
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
5044
5003
|
const signal = await Promise.race([
|
|
5045
|
-
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
|
|
5004
|
+
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
|
|
5046
5005
|
sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
|
|
5047
5006
|
]);
|
|
5048
5007
|
if (typeof signal === "symbol") {
|
|
@@ -5072,7 +5031,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5072
5031
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5073
5032
|
priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
|
|
5074
5033
|
position: signal.position,
|
|
5075
|
-
note:
|
|
5034
|
+
note: signal.note || "",
|
|
5076
5035
|
priceTakeProfit: signal.priceTakeProfit,
|
|
5077
5036
|
priceStopLoss: signal.priceStopLoss,
|
|
5078
5037
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
@@ -5098,7 +5057,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5098
5057
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5099
5058
|
priceOpen: signal.priceOpen,
|
|
5100
5059
|
position: signal.position,
|
|
5101
|
-
note:
|
|
5060
|
+
note: signal.note || "",
|
|
5102
5061
|
priceTakeProfit: signal.priceTakeProfit,
|
|
5103
5062
|
priceStopLoss: signal.priceStopLoss,
|
|
5104
5063
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
@@ -5123,7 +5082,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
5123
5082
|
cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
5124
5083
|
priceOpen: currentPrice,
|
|
5125
5084
|
...structuredClone(signal),
|
|
5126
|
-
note:
|
|
5085
|
+
note: signal.note || "",
|
|
5127
5086
|
minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
|
|
5128
5087
|
symbol: self.params.execution.context.symbol,
|
|
5129
5088
|
exchangeName: self.params.method.context.exchangeName,
|
|
@@ -5870,6 +5829,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
5870
5829
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
5871
5830
|
originalPriceOpen: scheduled.priceOpen,
|
|
5872
5831
|
pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
|
|
5832
|
+
note: scheduled.note,
|
|
5873
5833
|
});
|
|
5874
5834
|
return null;
|
|
5875
5835
|
}
|
|
@@ -6638,6 +6598,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
6638
6598
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6639
6599
|
originalPriceOpen: scheduled.priceOpen,
|
|
6640
6600
|
pnl: toProfitLossDto(scheduled, averagePrice),
|
|
6601
|
+
note: scheduled.note,
|
|
6641
6602
|
});
|
|
6642
6603
|
}
|
|
6643
6604
|
await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6716,6 +6677,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
6716
6677
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6717
6678
|
originalPriceOpen: scheduled.priceOpen,
|
|
6718
6679
|
pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
|
|
6680
|
+
note: scheduled.note,
|
|
6719
6681
|
});
|
|
6720
6682
|
return false;
|
|
6721
6683
|
}
|
|
@@ -6803,6 +6765,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
|
|
|
6803
6765
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
6804
6766
|
originalPriceOpen: closedSignal.priceOpen,
|
|
6805
6767
|
pnl: toProfitLossDto(closedSignal, averagePrice),
|
|
6768
|
+
note: closedSignal.note,
|
|
6806
6769
|
});
|
|
6807
6770
|
await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
6808
6771
|
await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6903,6 +6866,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6903
6866
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
6904
6867
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
6905
6868
|
pnl: toProfitLossDto(activatedSignal, averagePrice),
|
|
6869
|
+
note: activatedSignal.note,
|
|
6906
6870
|
});
|
|
6907
6871
|
return { outcome: "pending" };
|
|
6908
6872
|
}
|
|
@@ -6934,6 +6898,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6934
6898
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
6935
6899
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
6936
6900
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
6901
|
+
note: publicSignalForCommit.note,
|
|
6937
6902
|
});
|
|
6938
6903
|
await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
|
|
6939
6904
|
await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
|
|
@@ -7970,6 +7935,90 @@ class ClientStrategy {
|
|
|
7970
7935
|
}
|
|
7971
7936
|
return this._pendingSignal._fall.pnlCost;
|
|
7972
7937
|
}
|
|
7938
|
+
/**
|
|
7939
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
7940
|
+
*
|
|
7941
|
+
* Measures how much PnL% the position has given back from its best point.
|
|
7942
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
7943
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
7944
|
+
*
|
|
7945
|
+
* Returns null if no pending signal exists.
|
|
7946
|
+
*
|
|
7947
|
+
* @param symbol - Trading pair symbol
|
|
7948
|
+
* @param currentPrice - Current market price
|
|
7949
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
7950
|
+
*/
|
|
7951
|
+
async getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice) {
|
|
7952
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlPercentage", { symbol, currentPrice });
|
|
7953
|
+
if (!this._pendingSignal) {
|
|
7954
|
+
return null;
|
|
7955
|
+
}
|
|
7956
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
7957
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - currentPnl.pnlPercentage);
|
|
7958
|
+
}
|
|
7959
|
+
/**
|
|
7960
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
7961
|
+
*
|
|
7962
|
+
* Measures how much PnL cost the position has given back from its best point.
|
|
7963
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
7964
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
7965
|
+
*
|
|
7966
|
+
* Returns null if no pending signal exists.
|
|
7967
|
+
*
|
|
7968
|
+
* @param symbol - Trading pair symbol
|
|
7969
|
+
* @param currentPrice - Current market price
|
|
7970
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
7971
|
+
*/
|
|
7972
|
+
async getPositionHighestProfitDistancePnlCost(symbol, currentPrice) {
|
|
7973
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlCost", { symbol, currentPrice });
|
|
7974
|
+
if (!this._pendingSignal) {
|
|
7975
|
+
return null;
|
|
7976
|
+
}
|
|
7977
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
7978
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - currentPnl.pnlCost);
|
|
7979
|
+
}
|
|
7980
|
+
/**
|
|
7981
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
7982
|
+
*
|
|
7983
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
7984
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
7985
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
7986
|
+
*
|
|
7987
|
+
* Returns null if no pending signal exists.
|
|
7988
|
+
*
|
|
7989
|
+
* @param symbol - Trading pair symbol
|
|
7990
|
+
* @param currentPrice - Current market price
|
|
7991
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
7992
|
+
*/
|
|
7993
|
+
async getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice) {
|
|
7994
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlPercentage", { symbol, currentPrice });
|
|
7995
|
+
if (!this._pendingSignal) {
|
|
7996
|
+
return null;
|
|
7997
|
+
}
|
|
7998
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
7999
|
+
return Math.max(0, currentPnl.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8000
|
+
}
|
|
8001
|
+
/**
|
|
8002
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
8003
|
+
*
|
|
8004
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
8005
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
8006
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
8007
|
+
*
|
|
8008
|
+
* Returns null if no pending signal exists.
|
|
8009
|
+
*
|
|
8010
|
+
* @param symbol - Trading pair symbol
|
|
8011
|
+
* @param currentPrice - Current market price
|
|
8012
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
8013
|
+
*/
|
|
8014
|
+
async getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice) {
|
|
8015
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlCost", { symbol, currentPrice });
|
|
8016
|
+
if (!this._pendingSignal) {
|
|
8017
|
+
return null;
|
|
8018
|
+
}
|
|
8019
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8020
|
+
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8021
|
+
}
|
|
7973
8022
|
/**
|
|
7974
8023
|
* Performs a single tick of strategy execution.
|
|
7975
8024
|
*
|
|
@@ -8034,6 +8083,7 @@ class ClientStrategy {
|
|
|
8034
8083
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8035
8084
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8036
8085
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8086
|
+
note: cancelledSignal.note,
|
|
8037
8087
|
});
|
|
8038
8088
|
// Call onCancel callback
|
|
8039
8089
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8087,6 +8137,7 @@ class ClientStrategy {
|
|
|
8087
8137
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8088
8138
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8089
8139
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8140
|
+
note: closedSignal.note,
|
|
8090
8141
|
});
|
|
8091
8142
|
// Call onClose callback
|
|
8092
8143
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8168,6 +8219,7 @@ class ClientStrategy {
|
|
|
8168
8219
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
8169
8220
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
8170
8221
|
pnl: toProfitLossDto(activatedSignal, currentPrice),
|
|
8222
|
+
note: activatedSignal.note,
|
|
8171
8223
|
});
|
|
8172
8224
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
8173
8225
|
}
|
|
@@ -8198,6 +8250,7 @@ class ClientStrategy {
|
|
|
8198
8250
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
8199
8251
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
8200
8252
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
8253
|
+
note: publicSignalForCommit.note,
|
|
8201
8254
|
});
|
|
8202
8255
|
// Call onOpen callback
|
|
8203
8256
|
await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8333,6 +8386,7 @@ class ClientStrategy {
|
|
|
8333
8386
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8334
8387
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8335
8388
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8389
|
+
note: cancelledSignal.note,
|
|
8336
8390
|
});
|
|
8337
8391
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8338
8392
|
const cancelledResult = {
|
|
@@ -8387,6 +8441,7 @@ class ClientStrategy {
|
|
|
8387
8441
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8388
8442
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8389
8443
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8444
|
+
note: closedSignal.note,
|
|
8390
8445
|
});
|
|
8391
8446
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8392
8447
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
@@ -9936,6 +9991,8 @@ class MergeRisk {
|
|
|
9936
9991
|
}
|
|
9937
9992
|
}
|
|
9938
9993
|
|
|
9994
|
+
/** Default interval for strategies that do not specify one */
|
|
9995
|
+
const STRATEGY_DEFAULT_INTERVAL = "1m";
|
|
9939
9996
|
/**
|
|
9940
9997
|
* If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
|
|
9941
9998
|
* to the exchange (e.g. limit order failed to fill).
|
|
@@ -10321,7 +10378,7 @@ class StrategyConnectionService {
|
|
|
10321
10378
|
* @returns Configured ClientStrategy instance
|
|
10322
10379
|
*/
|
|
10323
10380
|
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10324
|
-
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10381
|
+
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10325
10382
|
return new ClientStrategy({
|
|
10326
10383
|
symbol,
|
|
10327
10384
|
interval,
|
|
@@ -11077,6 +11134,90 @@ class StrategyConnectionService {
|
|
|
11077
11134
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11078
11135
|
return await strategy.getPositionMaxDrawdownPnlCost(symbol);
|
|
11079
11136
|
};
|
|
11137
|
+
/**
|
|
11138
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
11139
|
+
*
|
|
11140
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11141
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlPercentage().
|
|
11142
|
+
* Returns null if no pending signal exists.
|
|
11143
|
+
*
|
|
11144
|
+
* @param backtest - Whether running in backtest mode
|
|
11145
|
+
* @param symbol - Trading pair symbol
|
|
11146
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11147
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
11148
|
+
*/
|
|
11149
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11150
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlPercentage", {
|
|
11151
|
+
symbol,
|
|
11152
|
+
context,
|
|
11153
|
+
});
|
|
11154
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11155
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11156
|
+
return await strategy.getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice);
|
|
11157
|
+
};
|
|
11158
|
+
/**
|
|
11159
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
11160
|
+
*
|
|
11161
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11162
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlCost().
|
|
11163
|
+
* Returns null if no pending signal exists.
|
|
11164
|
+
*
|
|
11165
|
+
* @param backtest - Whether running in backtest mode
|
|
11166
|
+
* @param symbol - Trading pair symbol
|
|
11167
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11168
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
11169
|
+
*/
|
|
11170
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
11171
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlCost", {
|
|
11172
|
+
symbol,
|
|
11173
|
+
context,
|
|
11174
|
+
});
|
|
11175
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11176
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11177
|
+
return await strategy.getPositionHighestProfitDistancePnlCost(symbol, currentPrice);
|
|
11178
|
+
};
|
|
11179
|
+
/**
|
|
11180
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
11181
|
+
*
|
|
11182
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11183
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlPercentage().
|
|
11184
|
+
* Returns null if no pending signal exists.
|
|
11185
|
+
*
|
|
11186
|
+
* @param backtest - Whether running in backtest mode
|
|
11187
|
+
* @param symbol - Trading pair symbol
|
|
11188
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11189
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
11190
|
+
*/
|
|
11191
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
11192
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
11193
|
+
symbol,
|
|
11194
|
+
context,
|
|
11195
|
+
});
|
|
11196
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11197
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11198
|
+
return await strategy.getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice);
|
|
11199
|
+
};
|
|
11200
|
+
/**
|
|
11201
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
11202
|
+
*
|
|
11203
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11204
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlCost().
|
|
11205
|
+
* Returns null if no pending signal exists.
|
|
11206
|
+
*
|
|
11207
|
+
* @param backtest - Whether running in backtest mode
|
|
11208
|
+
* @param symbol - Trading pair symbol
|
|
11209
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11210
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
11211
|
+
*/
|
|
11212
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
11213
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlCost", {
|
|
11214
|
+
symbol,
|
|
11215
|
+
context,
|
|
11216
|
+
});
|
|
11217
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11218
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11219
|
+
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11220
|
+
};
|
|
11080
11221
|
/**
|
|
11081
11222
|
* Disposes the ClientStrategy instance for the given context.
|
|
11082
11223
|
*
|
|
@@ -15238,6 +15379,82 @@ class StrategyCoreService {
|
|
|
15238
15379
|
await this.validate(context);
|
|
15239
15380
|
return await this.strategyConnectionService.getPositionMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15240
15381
|
};
|
|
15382
|
+
/**
|
|
15383
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
15384
|
+
*
|
|
15385
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlPercentage().
|
|
15386
|
+
* Returns null if no pending signal exists.
|
|
15387
|
+
*
|
|
15388
|
+
* @param backtest - Whether running in backtest mode
|
|
15389
|
+
* @param symbol - Trading pair symbol
|
|
15390
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15391
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
15392
|
+
*/
|
|
15393
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15394
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlPercentage", {
|
|
15395
|
+
symbol,
|
|
15396
|
+
context,
|
|
15397
|
+
});
|
|
15398
|
+
await this.validate(context);
|
|
15399
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlPercentage(backtest, symbol, context);
|
|
15400
|
+
};
|
|
15401
|
+
/**
|
|
15402
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
15403
|
+
*
|
|
15404
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlCost().
|
|
15405
|
+
* Returns null if no pending signal exists.
|
|
15406
|
+
*
|
|
15407
|
+
* @param backtest - Whether running in backtest mode
|
|
15408
|
+
* @param symbol - Trading pair symbol
|
|
15409
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15410
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
15411
|
+
*/
|
|
15412
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
15413
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlCost", {
|
|
15414
|
+
symbol,
|
|
15415
|
+
context,
|
|
15416
|
+
});
|
|
15417
|
+
await this.validate(context);
|
|
15418
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlCost(backtest, symbol, context);
|
|
15419
|
+
};
|
|
15420
|
+
/**
|
|
15421
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
15422
|
+
*
|
|
15423
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage().
|
|
15424
|
+
* Returns null if no pending signal exists.
|
|
15425
|
+
*
|
|
15426
|
+
* @param backtest - Whether running in backtest mode
|
|
15427
|
+
* @param symbol - Trading pair symbol
|
|
15428
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15429
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
15430
|
+
*/
|
|
15431
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
15432
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
15433
|
+
symbol,
|
|
15434
|
+
context,
|
|
15435
|
+
});
|
|
15436
|
+
await this.validate(context);
|
|
15437
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage(backtest, symbol, context);
|
|
15438
|
+
};
|
|
15439
|
+
/**
|
|
15440
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
15441
|
+
*
|
|
15442
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlCost().
|
|
15443
|
+
* Returns null if no pending signal exists.
|
|
15444
|
+
*
|
|
15445
|
+
* @param backtest - Whether running in backtest mode
|
|
15446
|
+
* @param symbol - Trading pair symbol
|
|
15447
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15448
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
15449
|
+
*/
|
|
15450
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
15451
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlCost", {
|
|
15452
|
+
symbol,
|
|
15453
|
+
context,
|
|
15454
|
+
});
|
|
15455
|
+
await this.validate(context);
|
|
15456
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15457
|
+
};
|
|
15241
15458
|
}
|
|
15242
15459
|
}
|
|
15243
15460
|
|
|
@@ -15880,8 +16097,8 @@ class StrategySchemaService {
|
|
|
15880
16097
|
if (strategySchema.actions?.some((value) => typeof value !== "string")) {
|
|
15881
16098
|
throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
|
|
15882
16099
|
}
|
|
15883
|
-
if (typeof strategySchema.interval !== "string") {
|
|
15884
|
-
throw new Error(`strategy schema validation failed:
|
|
16100
|
+
if (strategySchema.interval && typeof strategySchema.interval !== "string") {
|
|
16101
|
+
throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
|
|
15885
16102
|
}
|
|
15886
16103
|
if (typeof strategySchema.getSignal !== "function") {
|
|
15887
16104
|
throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
|
|
@@ -17827,6 +18044,53 @@ class WalkerCommandService {
|
|
|
17827
18044
|
}
|
|
17828
18045
|
}
|
|
17829
18046
|
|
|
18047
|
+
/**
|
|
18048
|
+
* Converts markdown content to plain text with minimal formatting
|
|
18049
|
+
* @param content - Markdown string to convert
|
|
18050
|
+
* @returns Plain text representation
|
|
18051
|
+
*/
|
|
18052
|
+
const toPlainString = (content) => {
|
|
18053
|
+
if (!content) {
|
|
18054
|
+
return "";
|
|
18055
|
+
}
|
|
18056
|
+
let text = content;
|
|
18057
|
+
// Remove code blocks
|
|
18058
|
+
text = text.replace(/```[\s\S]*?```/g, "");
|
|
18059
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
18060
|
+
// Remove images
|
|
18061
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
18062
|
+
// Convert links to text only (keep link text, remove URL)
|
|
18063
|
+
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
18064
|
+
// Remove headers (convert to plain text)
|
|
18065
|
+
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
18066
|
+
// Remove bold and italic markers
|
|
18067
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
18068
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
18069
|
+
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
18070
|
+
text = text.replace(/___(.+?)___/g, "$1");
|
|
18071
|
+
text = text.replace(/__(.+?)__/g, "$1");
|
|
18072
|
+
text = text.replace(/_(.+?)_/g, "$1");
|
|
18073
|
+
// Remove strikethrough
|
|
18074
|
+
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
18075
|
+
// Convert lists to plain text with bullets
|
|
18076
|
+
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
18077
|
+
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
18078
|
+
// Remove blockquotes
|
|
18079
|
+
text = text.replace(/^\s*>\s+/gm, "");
|
|
18080
|
+
// Remove horizontal rules
|
|
18081
|
+
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
18082
|
+
// Remove HTML tags
|
|
18083
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
18084
|
+
// Remove excessive whitespace and normalize line breaks
|
|
18085
|
+
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
18086
|
+
text = text.replace(/[ \t]+/g, " ");
|
|
18087
|
+
// Remove all newline characters
|
|
18088
|
+
text = text.replace(/\n/g, " ");
|
|
18089
|
+
// Remove excessive spaces after newline removal
|
|
18090
|
+
text = text.replace(/\s+/g, " ");
|
|
18091
|
+
return text.trim();
|
|
18092
|
+
};
|
|
18093
|
+
|
|
17830
18094
|
/**
|
|
17831
18095
|
* Column configuration for backtest markdown reports.
|
|
17832
18096
|
*
|
|
@@ -18502,7 +18766,7 @@ const partial_columns = [
|
|
|
18502
18766
|
{
|
|
18503
18767
|
key: "note",
|
|
18504
18768
|
label: "Note",
|
|
18505
|
-
format: (data) => data.note
|
|
18769
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
18506
18770
|
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
18507
18771
|
},
|
|
18508
18772
|
{
|
|
@@ -18662,7 +18926,7 @@ const breakeven_columns = [
|
|
|
18662
18926
|
{
|
|
18663
18927
|
key: "note",
|
|
18664
18928
|
label: "Note",
|
|
18665
|
-
format: (data) => data.note
|
|
18929
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
18666
18930
|
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
18667
18931
|
},
|
|
18668
18932
|
{
|
|
@@ -34893,6 +35157,10 @@ const GET_POSITION_MAX_DRAWDOWN_PRICE_METHOD_NAME = "strategy.getPositionMaxDraw
|
|
|
34893
35157
|
const GET_POSITION_MAX_DRAWDOWN_TIMESTAMP_METHOD_NAME = "strategy.getPositionMaxDrawdownTimestamp";
|
|
34894
35158
|
const GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlPercentage";
|
|
34895
35159
|
const GET_POSITION_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlCost";
|
|
35160
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlPercentage";
|
|
35161
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35162
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35163
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
34896
35164
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
34897
35165
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
34898
35166
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -36527,6 +36795,122 @@ async function getPositionMaxDrawdownPnlCost(symbol) {
|
|
|
36527
36795
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36528
36796
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36529
36797
|
}
|
|
36798
|
+
/**
|
|
36799
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
36800
|
+
*
|
|
36801
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
36802
|
+
* Returns null if no pending signal exists.
|
|
36803
|
+
*
|
|
36804
|
+
* @param symbol - Trading pair symbol
|
|
36805
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
36806
|
+
*
|
|
36807
|
+
* @example
|
|
36808
|
+
* ```typescript
|
|
36809
|
+
* import { getPositionHighestProfitDistancePnlPercentage } from "backtest-kit";
|
|
36810
|
+
*
|
|
36811
|
+
* const dist = await getPositionHighestProfitDistancePnlPercentage("BTCUSDT");
|
|
36812
|
+
* // e.g. 1.5 (gave back 1.5% from peak)
|
|
36813
|
+
* ```
|
|
36814
|
+
*/
|
|
36815
|
+
async function getPositionHighestProfitDistancePnlPercentage(symbol) {
|
|
36816
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36817
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36818
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires an execution context");
|
|
36819
|
+
}
|
|
36820
|
+
if (!MethodContextService.hasContext()) {
|
|
36821
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires a method context");
|
|
36822
|
+
}
|
|
36823
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36824
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36825
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36826
|
+
}
|
|
36827
|
+
/**
|
|
36828
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
36829
|
+
*
|
|
36830
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
36831
|
+
* Returns null if no pending signal exists.
|
|
36832
|
+
*
|
|
36833
|
+
* @param symbol - Trading pair symbol
|
|
36834
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
36835
|
+
*
|
|
36836
|
+
* @example
|
|
36837
|
+
* ```typescript
|
|
36838
|
+
* import { getPositionHighestProfitDistancePnlCost } from "backtest-kit";
|
|
36839
|
+
*
|
|
36840
|
+
* const dist = await getPositionHighestProfitDistancePnlCost("BTCUSDT");
|
|
36841
|
+
* // e.g. 3.2 (gave back $3.2 from peak)
|
|
36842
|
+
* ```
|
|
36843
|
+
*/
|
|
36844
|
+
async function getPositionHighestProfitDistancePnlCost(symbol) {
|
|
36845
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
36846
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36847
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires an execution context");
|
|
36848
|
+
}
|
|
36849
|
+
if (!MethodContextService.hasContext()) {
|
|
36850
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires a method context");
|
|
36851
|
+
}
|
|
36852
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36853
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36854
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36855
|
+
}
|
|
36856
|
+
/**
|
|
36857
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
36858
|
+
*
|
|
36859
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
36860
|
+
* Returns null if no pending signal exists.
|
|
36861
|
+
*
|
|
36862
|
+
* @param symbol - Trading pair symbol
|
|
36863
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
36864
|
+
*
|
|
36865
|
+
* @example
|
|
36866
|
+
* ```typescript
|
|
36867
|
+
* import { getPositionHighestMaxDrawdownPnlPercentage } from "backtest-kit";
|
|
36868
|
+
*
|
|
36869
|
+
* const dist = await getPositionHighestMaxDrawdownPnlPercentage("BTCUSDT");
|
|
36870
|
+
* // e.g. 2.1 (recovered 2.1% from trough)
|
|
36871
|
+
* ```
|
|
36872
|
+
*/
|
|
36873
|
+
async function getPositionHighestMaxDrawdownPnlPercentage(symbol) {
|
|
36874
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36875
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36876
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires an execution context");
|
|
36877
|
+
}
|
|
36878
|
+
if (!MethodContextService.hasContext()) {
|
|
36879
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires a method context");
|
|
36880
|
+
}
|
|
36881
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36882
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36883
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36884
|
+
}
|
|
36885
|
+
/**
|
|
36886
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
36887
|
+
*
|
|
36888
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
36889
|
+
* Returns null if no pending signal exists.
|
|
36890
|
+
*
|
|
36891
|
+
* @param symbol - Trading pair symbol
|
|
36892
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
36893
|
+
*
|
|
36894
|
+
* @example
|
|
36895
|
+
* ```typescript
|
|
36896
|
+
* import { getPositionHighestMaxDrawdownPnlCost } from "backtest-kit";
|
|
36897
|
+
*
|
|
36898
|
+
* const dist = await getPositionHighestMaxDrawdownPnlCost("BTCUSDT");
|
|
36899
|
+
* // e.g. 4.8 (recovered $4.8 from trough)
|
|
36900
|
+
* ```
|
|
36901
|
+
*/
|
|
36902
|
+
async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
36903
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME, { symbol });
|
|
36904
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36905
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires an execution context");
|
|
36906
|
+
}
|
|
36907
|
+
if (!MethodContextService.hasContext()) {
|
|
36908
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires a method context");
|
|
36909
|
+
}
|
|
36910
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36911
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36912
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36913
|
+
}
|
|
36530
36914
|
/**
|
|
36531
36915
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36532
36916
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38196,6 +38580,10 @@ const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "BacktestUtils.getP
|
|
|
38196
38580
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "BacktestUtils.getPositionMaxDrawdownTimestamp";
|
|
38197
38581
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionMaxDrawdownPnlPercentage";
|
|
38198
38582
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionMaxDrawdownPnlCost";
|
|
38583
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
38584
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38585
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38586
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38199
38587
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38200
38588
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38201
38589
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39465,6 +39853,118 @@ class BacktestUtils {
|
|
|
39465
39853
|
}
|
|
39466
39854
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(true, symbol, context);
|
|
39467
39855
|
};
|
|
39856
|
+
/**
|
|
39857
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
39858
|
+
*
|
|
39859
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
39860
|
+
* Returns null if no pending signal exists.
|
|
39861
|
+
*
|
|
39862
|
+
* @param symbol - Trading pair symbol
|
|
39863
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39864
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
39865
|
+
*/
|
|
39866
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
39867
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
39868
|
+
symbol,
|
|
39869
|
+
context,
|
|
39870
|
+
});
|
|
39871
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39872
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39873
|
+
{
|
|
39874
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39875
|
+
riskName &&
|
|
39876
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39877
|
+
riskList &&
|
|
39878
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39879
|
+
actions &&
|
|
39880
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39881
|
+
}
|
|
39882
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(true, symbol, context);
|
|
39883
|
+
};
|
|
39884
|
+
/**
|
|
39885
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
39886
|
+
*
|
|
39887
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
39888
|
+
* Returns null if no pending signal exists.
|
|
39889
|
+
*
|
|
39890
|
+
* @param symbol - Trading pair symbol
|
|
39891
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39892
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
39893
|
+
*/
|
|
39894
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
39895
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
39896
|
+
symbol,
|
|
39897
|
+
context,
|
|
39898
|
+
});
|
|
39899
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39900
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39901
|
+
{
|
|
39902
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39903
|
+
riskName &&
|
|
39904
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39905
|
+
riskList &&
|
|
39906
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39907
|
+
actions &&
|
|
39908
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39909
|
+
}
|
|
39910
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(true, symbol, context);
|
|
39911
|
+
};
|
|
39912
|
+
/**
|
|
39913
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
39914
|
+
*
|
|
39915
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
39916
|
+
* Returns null if no pending signal exists.
|
|
39917
|
+
*
|
|
39918
|
+
* @param symbol - Trading pair symbol
|
|
39919
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39920
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
39921
|
+
*/
|
|
39922
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
39923
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
39924
|
+
symbol,
|
|
39925
|
+
context,
|
|
39926
|
+
});
|
|
39927
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39928
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39929
|
+
{
|
|
39930
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39931
|
+
riskName &&
|
|
39932
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39933
|
+
riskList &&
|
|
39934
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39935
|
+
actions &&
|
|
39936
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39937
|
+
}
|
|
39938
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(true, symbol, context);
|
|
39939
|
+
};
|
|
39940
|
+
/**
|
|
39941
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
39942
|
+
*
|
|
39943
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
39944
|
+
* Returns null if no pending signal exists.
|
|
39945
|
+
*
|
|
39946
|
+
* @param symbol - Trading pair symbol
|
|
39947
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39948
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
39949
|
+
*/
|
|
39950
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
39951
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
39952
|
+
symbol,
|
|
39953
|
+
context,
|
|
39954
|
+
});
|
|
39955
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39956
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39957
|
+
{
|
|
39958
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39959
|
+
riskName &&
|
|
39960
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39961
|
+
riskList &&
|
|
39962
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39963
|
+
actions &&
|
|
39964
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39965
|
+
}
|
|
39966
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39967
|
+
};
|
|
39468
39968
|
/**
|
|
39469
39969
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39470
39970
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40590,6 +41090,10 @@ const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "LiveUtils.getPositionM
|
|
|
40590
41090
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "LiveUtils.getPositionMaxDrawdownTimestamp";
|
|
40591
41091
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionMaxDrawdownPnlPercentage";
|
|
40592
41092
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionMaxDrawdownPnlCost";
|
|
41093
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
41094
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41095
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41096
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
40593
41097
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
40594
41098
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
40595
41099
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -41986,6 +42490,134 @@ class LiveUtils {
|
|
|
41986
42490
|
frameName: "",
|
|
41987
42491
|
});
|
|
41988
42492
|
};
|
|
42493
|
+
/**
|
|
42494
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
42495
|
+
*
|
|
42496
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
42497
|
+
* Returns null if no pending signal exists.
|
|
42498
|
+
*
|
|
42499
|
+
* @param symbol - Trading pair symbol
|
|
42500
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42501
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
42502
|
+
*/
|
|
42503
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
42504
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
42505
|
+
symbol,
|
|
42506
|
+
context,
|
|
42507
|
+
});
|
|
42508
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42509
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42510
|
+
{
|
|
42511
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42512
|
+
riskName &&
|
|
42513
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42514
|
+
riskList &&
|
|
42515
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42516
|
+
actions &&
|
|
42517
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42518
|
+
}
|
|
42519
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(false, symbol, {
|
|
42520
|
+
strategyName: context.strategyName,
|
|
42521
|
+
exchangeName: context.exchangeName,
|
|
42522
|
+
frameName: "",
|
|
42523
|
+
});
|
|
42524
|
+
};
|
|
42525
|
+
/**
|
|
42526
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
42527
|
+
*
|
|
42528
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
42529
|
+
* Returns null if no pending signal exists.
|
|
42530
|
+
*
|
|
42531
|
+
* @param symbol - Trading pair symbol
|
|
42532
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42533
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
42534
|
+
*/
|
|
42535
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
42536
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
42537
|
+
symbol,
|
|
42538
|
+
context,
|
|
42539
|
+
});
|
|
42540
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42541
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42542
|
+
{
|
|
42543
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42544
|
+
riskName &&
|
|
42545
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42546
|
+
riskList &&
|
|
42547
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42548
|
+
actions &&
|
|
42549
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42550
|
+
}
|
|
42551
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(false, symbol, {
|
|
42552
|
+
strategyName: context.strategyName,
|
|
42553
|
+
exchangeName: context.exchangeName,
|
|
42554
|
+
frameName: "",
|
|
42555
|
+
});
|
|
42556
|
+
};
|
|
42557
|
+
/**
|
|
42558
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
42559
|
+
*
|
|
42560
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
42561
|
+
* Returns null if no pending signal exists.
|
|
42562
|
+
*
|
|
42563
|
+
* @param symbol - Trading pair symbol
|
|
42564
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42565
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
42566
|
+
*/
|
|
42567
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
42568
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
42569
|
+
symbol,
|
|
42570
|
+
context,
|
|
42571
|
+
});
|
|
42572
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42573
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42574
|
+
{
|
|
42575
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42576
|
+
riskName &&
|
|
42577
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42578
|
+
riskList &&
|
|
42579
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42580
|
+
actions &&
|
|
42581
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42582
|
+
}
|
|
42583
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(false, symbol, {
|
|
42584
|
+
strategyName: context.strategyName,
|
|
42585
|
+
exchangeName: context.exchangeName,
|
|
42586
|
+
frameName: "",
|
|
42587
|
+
});
|
|
42588
|
+
};
|
|
42589
|
+
/**
|
|
42590
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
42591
|
+
*
|
|
42592
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
42593
|
+
* Returns null if no pending signal exists.
|
|
42594
|
+
*
|
|
42595
|
+
* @param symbol - Trading pair symbol
|
|
42596
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42597
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
42598
|
+
*/
|
|
42599
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
42600
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
42601
|
+
symbol,
|
|
42602
|
+
context,
|
|
42603
|
+
});
|
|
42604
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42605
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42606
|
+
{
|
|
42607
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42608
|
+
riskName &&
|
|
42609
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42610
|
+
riskList &&
|
|
42611
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42612
|
+
actions &&
|
|
42613
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42614
|
+
}
|
|
42615
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(false, symbol, {
|
|
42616
|
+
strategyName: context.strategyName,
|
|
42617
|
+
exchangeName: context.exchangeName,
|
|
42618
|
+
frameName: "",
|
|
42619
|
+
});
|
|
42620
|
+
};
|
|
41989
42621
|
/**
|
|
41990
42622
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
41991
42623
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -51033,6 +51665,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
51033
51665
|
pnlEntries: data.signal.pnl.pnlEntries,
|
|
51034
51666
|
scheduledAt: data.signal.scheduledAt,
|
|
51035
51667
|
currentPrice: data.currentPrice,
|
|
51668
|
+
note: data.signal.note,
|
|
51036
51669
|
createdAt: data.createdAt,
|
|
51037
51670
|
};
|
|
51038
51671
|
}
|
|
@@ -51062,6 +51695,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
51062
51695
|
duration: durationMin,
|
|
51063
51696
|
scheduledAt: data.signal.scheduledAt,
|
|
51064
51697
|
pendingAt: data.signal.pendingAt,
|
|
51698
|
+
note: data.signal.note,
|
|
51065
51699
|
createdAt: data.createdAt,
|
|
51066
51700
|
};
|
|
51067
51701
|
}
|
|
@@ -51098,6 +51732,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
51098
51732
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51099
51733
|
pnlCost: data.data.pnl.pnlCost,
|
|
51100
51734
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
51735
|
+
note: data.data.note,
|
|
51101
51736
|
scheduledAt: data.data.scheduledAt,
|
|
51102
51737
|
pendingAt: data.data.pendingAt,
|
|
51103
51738
|
createdAt: data.timestamp,
|
|
@@ -51133,6 +51768,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
51133
51768
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51134
51769
|
pnlCost: data.data.pnl.pnlCost,
|
|
51135
51770
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
51771
|
+
note: data.data.note,
|
|
51136
51772
|
scheduledAt: data.data.scheduledAt,
|
|
51137
51773
|
pendingAt: data.data.pendingAt,
|
|
51138
51774
|
createdAt: data.timestamp,
|
|
@@ -51167,6 +51803,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
|
51167
51803
|
pnlPriceClose: data.data.pnl.priceClose,
|
|
51168
51804
|
pnlCost: data.data.pnl.pnlCost,
|
|
51169
51805
|
pnlEntries: data.data.pnl.pnlEntries,
|
|
51806
|
+
note: data.data.note,
|
|
51170
51807
|
scheduledAt: data.data.scheduledAt,
|
|
51171
51808
|
pendingAt: data.data.pendingAt,
|
|
51172
51809
|
createdAt: data.timestamp,
|
|
@@ -51208,6 +51845,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51208
51845
|
pnlEntries: data.pnl.pnlEntries,
|
|
51209
51846
|
scheduledAt: data.scheduledAt,
|
|
51210
51847
|
pendingAt: data.pendingAt,
|
|
51848
|
+
note: data.note,
|
|
51211
51849
|
createdAt: data.timestamp,
|
|
51212
51850
|
};
|
|
51213
51851
|
}
|
|
@@ -51240,6 +51878,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51240
51878
|
pnlEntries: data.pnl.pnlEntries,
|
|
51241
51879
|
scheduledAt: data.scheduledAt,
|
|
51242
51880
|
pendingAt: data.pendingAt,
|
|
51881
|
+
note: data.note,
|
|
51243
51882
|
createdAt: data.timestamp,
|
|
51244
51883
|
};
|
|
51245
51884
|
}
|
|
@@ -51271,6 +51910,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51271
51910
|
pnlEntries: data.pnl.pnlEntries,
|
|
51272
51911
|
scheduledAt: data.scheduledAt,
|
|
51273
51912
|
pendingAt: data.pendingAt,
|
|
51913
|
+
note: data.note,
|
|
51274
51914
|
createdAt: data.timestamp,
|
|
51275
51915
|
};
|
|
51276
51916
|
}
|
|
@@ -51303,6 +51943,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51303
51943
|
pnlEntries: data.pnl.pnlEntries,
|
|
51304
51944
|
scheduledAt: data.scheduledAt,
|
|
51305
51945
|
pendingAt: data.pendingAt,
|
|
51946
|
+
note: data.note,
|
|
51306
51947
|
createdAt: data.timestamp,
|
|
51307
51948
|
};
|
|
51308
51949
|
}
|
|
@@ -51335,6 +51976,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51335
51976
|
pnlEntries: data.pnl.pnlEntries,
|
|
51336
51977
|
scheduledAt: data.scheduledAt,
|
|
51337
51978
|
pendingAt: data.pendingAt,
|
|
51979
|
+
note: data.note,
|
|
51338
51980
|
createdAt: data.timestamp,
|
|
51339
51981
|
};
|
|
51340
51982
|
}
|
|
@@ -51367,6 +52009,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51367
52009
|
pnlEntries: data.pnl.pnlEntries,
|
|
51368
52010
|
scheduledAt: data.scheduledAt,
|
|
51369
52011
|
pendingAt: data.pendingAt,
|
|
52012
|
+
note: data.note,
|
|
51370
52013
|
createdAt: data.timestamp,
|
|
51371
52014
|
};
|
|
51372
52015
|
}
|
|
@@ -51400,6 +52043,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51400
52043
|
pnlEntries: data.pnl.pnlEntries,
|
|
51401
52044
|
scheduledAt: data.scheduledAt,
|
|
51402
52045
|
pendingAt: data.pendingAt,
|
|
52046
|
+
note: data.note,
|
|
51403
52047
|
createdAt: data.timestamp,
|
|
51404
52048
|
};
|
|
51405
52049
|
}
|
|
@@ -51423,6 +52067,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51423
52067
|
pnlPriceClose: data.pnl.priceClose,
|
|
51424
52068
|
pnlCost: data.pnl.pnlCost,
|
|
51425
52069
|
pnlEntries: data.pnl.pnlEntries,
|
|
52070
|
+
note: data.note,
|
|
51426
52071
|
createdAt: data.timestamp,
|
|
51427
52072
|
};
|
|
51428
52073
|
}
|
|
@@ -51446,6 +52091,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
51446
52091
|
pnlPriceClose: data.pnl.priceClose,
|
|
51447
52092
|
pnlCost: data.pnl.pnlCost,
|
|
51448
52093
|
pnlEntries: data.pnl.pnlEntries,
|
|
52094
|
+
note: data.note,
|
|
51449
52095
|
createdAt: data.timestamp,
|
|
51450
52096
|
};
|
|
51451
52097
|
}
|
|
@@ -51487,6 +52133,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51487
52133
|
totalPartials: data.totalPartials,
|
|
51488
52134
|
scheduledAt: data.scheduledAt,
|
|
51489
52135
|
pendingAt: data.pendingAt,
|
|
52136
|
+
note: data.signal.note,
|
|
51490
52137
|
createdAt: data.timestamp,
|
|
51491
52138
|
};
|
|
51492
52139
|
}
|
|
@@ -51519,6 +52166,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
|
|
|
51519
52166
|
scheduledAt: data.scheduledAt,
|
|
51520
52167
|
pendingAt: data.pendingAt,
|
|
51521
52168
|
closeReason: data.closeReason,
|
|
52169
|
+
note: data.signal.note,
|
|
51522
52170
|
createdAt: data.timestamp,
|
|
51523
52171
|
};
|
|
51524
52172
|
}
|
|
@@ -53018,6 +53666,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
|
|
|
53018
53666
|
const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
53019
53667
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
53020
53668
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
53669
|
+
const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
|
|
53021
53670
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
53022
53671
|
const MS_PER_MINUTE$1 = 60000;
|
|
53023
53672
|
const INTERVAL_MINUTES$1 = {
|
|
@@ -53269,7 +53918,7 @@ class CacheFileInstance {
|
|
|
53269
53918
|
/**
|
|
53270
53919
|
* Clears the index counter.
|
|
53271
53920
|
*/
|
|
53272
|
-
static
|
|
53921
|
+
static resetCounter() {
|
|
53273
53922
|
CacheFileInstance._indexCounter = 0;
|
|
53274
53923
|
}
|
|
53275
53924
|
/**
|
|
@@ -53524,11 +54173,17 @@ class CacheUtils {
|
|
|
53524
54173
|
*/
|
|
53525
54174
|
this.clear = () => {
|
|
53526
54175
|
backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
|
|
53527
|
-
|
|
53528
|
-
|
|
53529
|
-
|
|
53530
|
-
|
|
53531
|
-
|
|
54176
|
+
this._getFnInstance.clear();
|
|
54177
|
+
this._getFileInstance.clear();
|
|
54178
|
+
};
|
|
54179
|
+
/**
|
|
54180
|
+
* Resets the CacheFileInstance index counter to zero.
|
|
54181
|
+
* This is useful when process.cwd() changes between strategy iterations to ensure
|
|
54182
|
+
* that new CacheFileInstance objects start with index 0 and do not collide with old instances.
|
|
54183
|
+
*/
|
|
54184
|
+
this.resetCounter = () => {
|
|
54185
|
+
backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
|
|
54186
|
+
CacheFileInstance.resetCounter();
|
|
53532
54187
|
};
|
|
53533
54188
|
}
|
|
53534
54189
|
}
|
|
@@ -53552,10 +54207,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
|
53552
54207
|
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
53553
54208
|
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
53554
54209
|
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
54210
|
+
const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
|
|
53555
54211
|
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
53556
54212
|
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
53557
54213
|
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
53558
54214
|
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
54215
|
+
const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
|
|
53559
54216
|
const MS_PER_MINUTE = 60000;
|
|
53560
54217
|
const INTERVAL_MINUTES = {
|
|
53561
54218
|
"1m": 1,
|
|
@@ -53622,45 +54279,51 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
|
53622
54279
|
*
|
|
53623
54280
|
* State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
|
|
53624
54281
|
*
|
|
54282
|
+
* @template F - Concrete function type
|
|
54283
|
+
*
|
|
53625
54284
|
* @example
|
|
53626
54285
|
* ```typescript
|
|
53627
54286
|
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
53628
|
-
* await instance.run("BTCUSDT"); // →
|
|
53629
|
-
* await instance.run("BTCUSDT"); // → null
|
|
54287
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called)
|
|
54288
|
+
* await instance.run("BTCUSDT"); // → null (skipped, same interval)
|
|
53630
54289
|
* // After 1 hour passes:
|
|
53631
|
-
* await instance.run("BTCUSDT"); // →
|
|
54290
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called again)
|
|
53632
54291
|
* ```
|
|
53633
54292
|
*/
|
|
53634
54293
|
class IntervalFnInstance {
|
|
53635
54294
|
/**
|
|
53636
54295
|
* Creates a new IntervalFnInstance.
|
|
53637
54296
|
*
|
|
53638
|
-
* @param fn -
|
|
54297
|
+
* @param fn - Function to fire once per interval
|
|
53639
54298
|
* @param interval - Candle interval that controls the firing boundary
|
|
54299
|
+
* @param key - Optional key generator for argument-based state separation.
|
|
54300
|
+
* Default: `([symbol]) => symbol`
|
|
53640
54301
|
*/
|
|
53641
|
-
constructor(fn, interval) {
|
|
54302
|
+
constructor(fn, interval, key = ([symbol]) => symbol) {
|
|
53642
54303
|
this.fn = fn;
|
|
53643
54304
|
this.interval = interval;
|
|
53644
|
-
|
|
54305
|
+
this.key = key;
|
|
54306
|
+
/** Stores the last aligned timestamp per context+symbol+args key. */
|
|
53645
54307
|
this._stateMap = new Map();
|
|
53646
54308
|
/**
|
|
53647
54309
|
* Execute the signal function with once-per-interval enforcement.
|
|
53648
54310
|
*
|
|
53649
54311
|
* Algorithm:
|
|
53650
54312
|
* 1. Align the current execution context `when` to the interval boundary.
|
|
53651
|
-
* 2.
|
|
53652
|
-
* 3.
|
|
54313
|
+
* 2. Build state key from context + key generator result.
|
|
54314
|
+
* 3. If the stored aligned timestamp for this key equals the current one → return `null`.
|
|
54315
|
+
* 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
|
|
53653
54316
|
* the signal. If it returns `null`, leave state unchanged so the next call retries.
|
|
53654
54317
|
*
|
|
53655
54318
|
* Requires active method context and execution context.
|
|
53656
54319
|
*
|
|
53657
|
-
* @param
|
|
53658
|
-
* @returns The
|
|
54320
|
+
* @param args - Arguments forwarded to the wrapped function
|
|
54321
|
+
* @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
53659
54322
|
* within the same interval or when `fn` itself returned `null`
|
|
53660
54323
|
* @throws Error if method context, execution context, or interval is missing
|
|
53661
54324
|
*/
|
|
53662
|
-
this.run = async (
|
|
53663
|
-
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, {
|
|
54325
|
+
this.run = async (...args) => {
|
|
54326
|
+
backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
|
|
53664
54327
|
const step = INTERVAL_MINUTES[this.interval];
|
|
53665
54328
|
{
|
|
53666
54329
|
if (!MethodContextService.hasContext()) {
|
|
@@ -53674,15 +54337,16 @@ class IntervalFnInstance {
|
|
|
53674
54337
|
}
|
|
53675
54338
|
}
|
|
53676
54339
|
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
53677
|
-
const key = `${contextKey}:${symbol}`;
|
|
53678
54340
|
const currentWhen = backtest.executionContextService.context.when;
|
|
53679
54341
|
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
53680
|
-
|
|
54342
|
+
const argKey = this.key(args);
|
|
54343
|
+
const stateKey = `${contextKey}:${argKey}`;
|
|
54344
|
+
if (this._stateMap.get(stateKey) === currentAligned) {
|
|
53681
54345
|
return null;
|
|
53682
54346
|
}
|
|
53683
|
-
const result = await this.fn(
|
|
54347
|
+
const result = await this.fn.apply(null, args);
|
|
53684
54348
|
if (result !== null) {
|
|
53685
|
-
this._stateMap.set(
|
|
54349
|
+
this._stateMap.set(stateKey, currentAligned);
|
|
53686
54350
|
}
|
|
53687
54351
|
return result;
|
|
53688
54352
|
};
|
|
@@ -53703,6 +54367,28 @@ class IntervalFnInstance {
|
|
|
53703
54367
|
}
|
|
53704
54368
|
}
|
|
53705
54369
|
};
|
|
54370
|
+
/**
|
|
54371
|
+
* Garbage collect expired state entries.
|
|
54372
|
+
*
|
|
54373
|
+
* Removes all entries whose aligned timestamp differs from the current interval boundary.
|
|
54374
|
+
* Call this periodically to free memory from stale state entries.
|
|
54375
|
+
*
|
|
54376
|
+
* Requires active execution context to get current time.
|
|
54377
|
+
*
|
|
54378
|
+
* @returns Number of entries removed
|
|
54379
|
+
*/
|
|
54380
|
+
this.gc = () => {
|
|
54381
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
54382
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
54383
|
+
let removed = 0;
|
|
54384
|
+
for (const [key, storedAligned] of this._stateMap.entries()) {
|
|
54385
|
+
if (storedAligned !== currentAligned) {
|
|
54386
|
+
this._stateMap.delete(key);
|
|
54387
|
+
removed++;
|
|
54388
|
+
}
|
|
54389
|
+
}
|
|
54390
|
+
return removed;
|
|
54391
|
+
};
|
|
53706
54392
|
}
|
|
53707
54393
|
}
|
|
53708
54394
|
/**
|
|
@@ -53716,13 +54402,13 @@ class IntervalFnInstance {
|
|
|
53716
54402
|
*
|
|
53717
54403
|
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
53718
54404
|
*
|
|
53719
|
-
* @template
|
|
54405
|
+
* @template F - Concrete async function type
|
|
53720
54406
|
*
|
|
53721
54407
|
* @example
|
|
53722
54408
|
* ```typescript
|
|
53723
54409
|
* const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
|
|
53724
|
-
* await instance.run("BTCUSDT"); // →
|
|
53725
|
-
* await instance.run("BTCUSDT"); // → null
|
|
54410
|
+
* await instance.run("BTCUSDT"); // → R | null (fn called, result written to disk)
|
|
54411
|
+
* await instance.run("BTCUSDT"); // → null (record exists, already fired)
|
|
53726
54412
|
* ```
|
|
53727
54413
|
*/
|
|
53728
54414
|
class IntervalFileInstance {
|
|
@@ -53737,7 +54423,7 @@ class IntervalFileInstance {
|
|
|
53737
54423
|
* Resets the index counter to zero.
|
|
53738
54424
|
* Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
|
|
53739
54425
|
*/
|
|
53740
|
-
static
|
|
54426
|
+
static resetCounter() {
|
|
53741
54427
|
IntervalFileInstance._indexCounter = 0;
|
|
53742
54428
|
}
|
|
53743
54429
|
/**
|
|
@@ -53746,26 +54432,30 @@ class IntervalFileInstance {
|
|
|
53746
54432
|
* @param fn - Async signal function to fire once per interval
|
|
53747
54433
|
* @param interval - Candle interval that controls the firing boundary
|
|
53748
54434
|
* @param name - Human-readable bucket name used as the directory prefix
|
|
54435
|
+
* @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
|
|
54436
|
+
* Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
|
|
53749
54437
|
*/
|
|
53750
|
-
constructor(fn, interval, name) {
|
|
54438
|
+
constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
|
|
53751
54439
|
this.fn = fn;
|
|
53752
54440
|
this.interval = interval;
|
|
53753
54441
|
this.name = name;
|
|
54442
|
+
this.key = key;
|
|
53754
54443
|
/**
|
|
53755
|
-
* Execute the async
|
|
54444
|
+
* Execute the async function with persistent once-per-interval enforcement.
|
|
53756
54445
|
*
|
|
53757
54446
|
* Algorithm:
|
|
53758
54447
|
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
53759
|
-
* 2. Align execution context `when` to interval boundary → `
|
|
53760
|
-
* 3. Build entity key
|
|
54448
|
+
* 2. Align execution context `when` to interval boundary → `alignedMs`.
|
|
54449
|
+
* 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
|
|
53761
54450
|
* 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
|
|
53762
54451
|
* 5. On hit — return `null` (interval already fired).
|
|
53763
54452
|
* 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
|
|
53764
54453
|
*
|
|
53765
54454
|
* Requires active method context and execution context.
|
|
53766
54455
|
*
|
|
53767
|
-
* @param
|
|
53768
|
-
* @
|
|
54456
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
54457
|
+
* @param args - Additional arguments forwarded to the wrapped function
|
|
54458
|
+
* @returns The value on the first non-null fire, `null` if already fired this interval
|
|
53769
54459
|
* or if `fn` itself returned `null`
|
|
53770
54460
|
* @throws Error if method context, execution context, or interval is missing
|
|
53771
54461
|
*/
|
|
@@ -53783,11 +54473,11 @@ class IntervalFileInstance {
|
|
|
53783
54473
|
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
53784
54474
|
}
|
|
53785
54475
|
}
|
|
53786
|
-
const [symbol] = args;
|
|
54476
|
+
const [symbol, ...rest] = args;
|
|
53787
54477
|
const { when } = backtest.executionContextService.context;
|
|
53788
|
-
const
|
|
54478
|
+
const alignedMs = align(when.getTime(), this.interval);
|
|
53789
54479
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
53790
|
-
const entityKey =
|
|
54480
|
+
const entityKey = this.key([symbol, alignedMs, ...rest]);
|
|
53791
54481
|
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
53792
54482
|
if (cached !== null) {
|
|
53793
54483
|
return null;
|
|
@@ -53823,8 +54513,8 @@ IntervalFileInstance._indexCounter = 0;
|
|
|
53823
54513
|
* import { Interval } from "./classes/Interval";
|
|
53824
54514
|
*
|
|
53825
54515
|
* const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53826
|
-
* await fireOncePerHour("BTCUSDT"
|
|
53827
|
-
* await fireOncePerHour("BTCUSDT"
|
|
54516
|
+
* await fireOncePerHour("BTCUSDT"); // fn called — returns its result
|
|
54517
|
+
* await fireOncePerHour("BTCUSDT"); // returns null (same interval)
|
|
53828
54518
|
* ```
|
|
53829
54519
|
*/
|
|
53830
54520
|
class IntervalUtils {
|
|
@@ -53833,12 +54523,12 @@ class IntervalUtils {
|
|
|
53833
54523
|
* Memoized factory to get or create an `IntervalFnInstance` for a function.
|
|
53834
54524
|
* Each function reference gets its own isolated instance.
|
|
53835
54525
|
*/
|
|
53836
|
-
this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
|
|
54526
|
+
this._getInstance = memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
|
|
53837
54527
|
/**
|
|
53838
54528
|
* Memoized factory to get or create an `IntervalFileInstance` for an async function.
|
|
53839
54529
|
* Each function reference gets its own isolated persistent instance.
|
|
53840
54530
|
*/
|
|
53841
|
-
this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
|
|
54531
|
+
this._getFileInstance = memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
|
|
53842
54532
|
/**
|
|
53843
54533
|
* Wrap a signal function with in-memory once-per-interval firing.
|
|
53844
54534
|
*
|
|
@@ -53850,21 +54540,30 @@ class IntervalUtils {
|
|
|
53850
54540
|
*
|
|
53851
54541
|
* @param run - Signal function to wrap
|
|
53852
54542
|
* @param context.interval - Candle interval that controls the firing boundary
|
|
53853
|
-
* @
|
|
54543
|
+
* @param context.key - Optional key generator for argument-based state separation
|
|
54544
|
+
* @returns Wrapped function with the same signature as `F`, plus a `clear()` method
|
|
53854
54545
|
*
|
|
53855
54546
|
* @example
|
|
53856
54547
|
* ```typescript
|
|
54548
|
+
* // Without extra args
|
|
53857
54549
|
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
54550
|
+
* await fireOnce("BTCUSDT"); // → T or null (fn called)
|
|
54551
|
+
* await fireOnce("BTCUSDT"); // → null (same interval, skipped)
|
|
53858
54552
|
*
|
|
53859
|
-
*
|
|
53860
|
-
*
|
|
54553
|
+
* // With extra args and key
|
|
54554
|
+
* const fireOnce = Interval.fn(mySignalFn, {
|
|
54555
|
+
* interval: "15m",
|
|
54556
|
+
* key: ([symbol, period]) => `${symbol}_${period}`,
|
|
54557
|
+
* });
|
|
54558
|
+
* await fireOnce("BTCUSDT", 14); // → T or null
|
|
54559
|
+
* await fireOnce("BTCUSDT", 28); // → T or null (separate state)
|
|
53861
54560
|
* ```
|
|
53862
54561
|
*/
|
|
53863
54562
|
this.fn = (run, context) => {
|
|
53864
54563
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
53865
|
-
const wrappedFn = (
|
|
53866
|
-
const instance = this._getInstance(run, context.interval);
|
|
53867
|
-
return instance.run(
|
|
54564
|
+
const wrappedFn = (...args) => {
|
|
54565
|
+
const instance = this._getInstance(run, context.interval, context.key);
|
|
54566
|
+
return instance.run(...args);
|
|
53868
54567
|
};
|
|
53869
54568
|
wrappedFn.clear = () => {
|
|
53870
54569
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
@@ -53878,6 +54577,14 @@ class IntervalUtils {
|
|
|
53878
54577
|
}
|
|
53879
54578
|
this._getInstance.get(run)?.clear();
|
|
53880
54579
|
};
|
|
54580
|
+
wrappedFn.gc = () => {
|
|
54581
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
|
|
54582
|
+
if (!ExecutionContextService.hasContext()) {
|
|
54583
|
+
backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
|
|
54584
|
+
return;
|
|
54585
|
+
}
|
|
54586
|
+
return this._getInstance.get(run)?.gc();
|
|
54587
|
+
};
|
|
53881
54588
|
return wrappedFn;
|
|
53882
54589
|
};
|
|
53883
54590
|
/**
|
|
@@ -53890,27 +54597,32 @@ class IntervalUtils {
|
|
|
53890
54597
|
* The `run` function reference is used as the memoization key for the underlying
|
|
53891
54598
|
* `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
|
|
53892
54599
|
*
|
|
53893
|
-
* @template
|
|
54600
|
+
* @template F - Concrete async function type
|
|
53894
54601
|
* @param run - Async signal function to wrap with persistent once-per-interval firing
|
|
53895
54602
|
* @param context.interval - Candle interval that controls the firing boundary
|
|
53896
54603
|
* @param context.name - Human-readable bucket name; becomes the directory prefix
|
|
53897
|
-
* @
|
|
53898
|
-
*
|
|
54604
|
+
* @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
|
|
54605
|
+
* Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
|
|
54606
|
+
* @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
|
|
53899
54607
|
*
|
|
53900
54608
|
* @example
|
|
53901
54609
|
* ```typescript
|
|
53902
54610
|
* const fetchSignal = async (symbol: string, period: number) => { ... };
|
|
53903
|
-
* const fireOnce = Interval.file(fetchSignal, {
|
|
53904
|
-
*
|
|
54611
|
+
* const fireOnce = Interval.file(fetchSignal, {
|
|
54612
|
+
* interval: "1h",
|
|
54613
|
+
* name: "fetchSignal",
|
|
54614
|
+
* key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
|
|
54615
|
+
* });
|
|
54616
|
+
* await fireOnce("BTCUSDT", 14);
|
|
53905
54617
|
* ```
|
|
53906
54618
|
*/
|
|
53907
54619
|
this.file = (run, context) => {
|
|
53908
54620
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
|
|
53909
54621
|
{
|
|
53910
|
-
this._getFileInstance(run, context.interval, context.name);
|
|
54622
|
+
this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53911
54623
|
}
|
|
53912
54624
|
const wrappedFn = (...args) => {
|
|
53913
|
-
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
54625
|
+
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
53914
54626
|
return instance.run(...args);
|
|
53915
54627
|
};
|
|
53916
54628
|
wrappedFn.clear = async () => {
|
|
@@ -53937,10 +54649,10 @@ class IntervalUtils {
|
|
|
53937
54649
|
this.dispose = (run) => {
|
|
53938
54650
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
|
|
53939
54651
|
this._getInstance.clear(run);
|
|
54652
|
+
this._getFileInstance.clear(run);
|
|
53940
54653
|
};
|
|
53941
54654
|
/**
|
|
53942
|
-
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects
|
|
53943
|
-
* resets the `IntervalFileInstance` index counter.
|
|
54655
|
+
* Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
|
|
53944
54656
|
* Call this when `process.cwd()` changes between strategy iterations
|
|
53945
54657
|
* so new instances are created with the updated base path.
|
|
53946
54658
|
*/
|
|
@@ -53948,7 +54660,15 @@ class IntervalUtils {
|
|
|
53948
54660
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
|
|
53949
54661
|
this._getInstance.clear();
|
|
53950
54662
|
this._getFileInstance.clear();
|
|
53951
|
-
|
|
54663
|
+
};
|
|
54664
|
+
/**
|
|
54665
|
+
* Resets the IntervalFileInstance index counter to zero.
|
|
54666
|
+
* This is useful when process.cwd() changes between strategy iterations to ensure
|
|
54667
|
+
* that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
|
|
54668
|
+
*/
|
|
54669
|
+
this.resetCounter = () => {
|
|
54670
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
|
|
54671
|
+
IntervalFileInstance.resetCounter();
|
|
53952
54672
|
};
|
|
53953
54673
|
}
|
|
53954
54674
|
}
|
|
@@ -55082,4 +55802,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
55082
55802
|
return !errors.length;
|
|
55083
55803
|
};
|
|
55084
55804
|
|
|
55085
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|
|
55805
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|