backtest-kit 6.11.0 → 6.13.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 +2081 -183
- package/build/index.mjs +2073 -184
- package/package.json +2 -2
- package/types.d.ts +1136 -85
package/build/index.mjs
CHANGED
|
@@ -982,6 +982,12 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
|
|
|
982
982
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
|
|
983
983
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
|
|
984
984
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
|
|
985
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
|
|
986
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
|
|
987
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
|
|
988
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON = "PersistRecentUtils.useJson";
|
|
989
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY = "PersistRecentUtils.useDummy";
|
|
990
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR = "PersistRecentUtils.clear";
|
|
985
991
|
const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
|
|
986
992
|
const BASE_UNLINK_RETRY_COUNT = 5;
|
|
987
993
|
const BASE_UNLINK_RETRY_DELAY = 1000;
|
|
@@ -2828,6 +2834,115 @@ class PersistMemoryUtils {
|
|
|
2828
2834
|
* ```
|
|
2829
2835
|
*/
|
|
2830
2836
|
const PersistMemoryAdapter = new PersistMemoryUtils();
|
|
2837
|
+
/**
|
|
2838
|
+
* Utility class for managing recent signal persistence.
|
|
2839
|
+
*
|
|
2840
|
+
* Features:
|
|
2841
|
+
* - Memoized storage instances per (symbol, strategyName, exchangeName, frameName) context
|
|
2842
|
+
* - Custom adapter support
|
|
2843
|
+
* - Atomic read/write operations
|
|
2844
|
+
* - Crash-safe recent signal state management
|
|
2845
|
+
*
|
|
2846
|
+
* Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
|
|
2847
|
+
*/
|
|
2848
|
+
class PersistRecentUtils {
|
|
2849
|
+
constructor() {
|
|
2850
|
+
this.PersistRecentFactory = PersistBase;
|
|
2851
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":"), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.PersistRecentFactory, [
|
|
2852
|
+
this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join("_"),
|
|
2853
|
+
`./dump/data/recent/`,
|
|
2854
|
+
]));
|
|
2855
|
+
/**
|
|
2856
|
+
* Reads the latest persisted recent signal for a given context.
|
|
2857
|
+
*
|
|
2858
|
+
* Returns null if no recent signal exists.
|
|
2859
|
+
*
|
|
2860
|
+
* @param symbol - Trading pair symbol
|
|
2861
|
+
* @param strategyName - Strategy identifier
|
|
2862
|
+
* @param exchangeName - Exchange identifier
|
|
2863
|
+
* @param frameName - Frame identifier
|
|
2864
|
+
* @returns Promise resolving to recent signal or null
|
|
2865
|
+
*/
|
|
2866
|
+
this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
2867
|
+
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
|
|
2868
|
+
const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
|
|
2869
|
+
const isInitial = !this.getStorage.has(key);
|
|
2870
|
+
const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
2871
|
+
await stateStorage.waitForInit(isInitial);
|
|
2872
|
+
if (await stateStorage.hasValue(symbol)) {
|
|
2873
|
+
return await stateStorage.readValue(symbol);
|
|
2874
|
+
}
|
|
2875
|
+
return null;
|
|
2876
|
+
};
|
|
2877
|
+
/**
|
|
2878
|
+
* Writes the latest recent signal to disk with atomic file writes.
|
|
2879
|
+
*
|
|
2880
|
+
* Uses symbol as the entity ID within the per-context storage instance.
|
|
2881
|
+
* Uses atomic writes to prevent corruption on crashes.
|
|
2882
|
+
*
|
|
2883
|
+
* @param signalRow - Recent signal data to persist
|
|
2884
|
+
* @param symbol - Trading pair symbol
|
|
2885
|
+
* @param strategyName - Strategy identifier
|
|
2886
|
+
* @param exchangeName - Exchange identifier
|
|
2887
|
+
* @param frameName - Frame identifier
|
|
2888
|
+
* @returns Promise that resolves when write is complete
|
|
2889
|
+
*/
|
|
2890
|
+
this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
2891
|
+
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2892
|
+
const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
|
|
2893
|
+
const isInitial = !this.getStorage.has(key);
|
|
2894
|
+
const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
2895
|
+
await stateStorage.waitForInit(isInitial);
|
|
2896
|
+
await stateStorage.writeValue(symbol, signalRow);
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
createKeyParts(symbol, strategyName, exchangeName, frameName, backtest) {
|
|
2900
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
2901
|
+
if (frameName)
|
|
2902
|
+
parts.push(frameName);
|
|
2903
|
+
parts.push(backtest ? "backtest" : "live");
|
|
2904
|
+
return parts;
|
|
2905
|
+
}
|
|
2906
|
+
/**
|
|
2907
|
+
* Registers a custom persistence adapter.
|
|
2908
|
+
*
|
|
2909
|
+
* @param Ctor - Custom PersistBase constructor
|
|
2910
|
+
*/
|
|
2911
|
+
usePersistRecentAdapter(Ctor) {
|
|
2912
|
+
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
|
|
2913
|
+
this.PersistRecentFactory = Ctor;
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Clears the memoized storage cache.
|
|
2917
|
+
* Call this when process.cwd() changes between strategy iterations
|
|
2918
|
+
* so new storage instances are created with the updated base path.
|
|
2919
|
+
*/
|
|
2920
|
+
clear() {
|
|
2921
|
+
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
|
|
2922
|
+
this.getStorage.clear();
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Switches to the default JSON persist adapter.
|
|
2926
|
+
* All future persistence writes will use JSON storage.
|
|
2927
|
+
*/
|
|
2928
|
+
useJson() {
|
|
2929
|
+
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
|
|
2930
|
+
this.usePersistRecentAdapter(PersistBase);
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Switches to a dummy persist adapter that discards all writes.
|
|
2934
|
+
* All future persistence writes will be no-ops.
|
|
2935
|
+
*/
|
|
2936
|
+
useDummy() {
|
|
2937
|
+
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2938
|
+
this.usePersistRecentAdapter(PersistDummy);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
/**
|
|
2942
|
+
* Global singleton instance of PersistRecentUtils.
|
|
2943
|
+
* Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
|
|
2944
|
+
*/
|
|
2945
|
+
const PersistRecentAdapter = new PersistRecentUtils();
|
|
2831
2946
|
|
|
2832
2947
|
var _a$2, _b$2;
|
|
2833
2948
|
const BUSY_DELAY = 100;
|
|
@@ -6573,7 +6688,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
6573
6688
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
6574
6689
|
return result;
|
|
6575
6690
|
};
|
|
6576
|
-
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
|
|
6691
|
+
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId, cancelNote) => {
|
|
6577
6692
|
self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
|
|
6578
6693
|
symbol: self.params.execution.context.symbol,
|
|
6579
6694
|
signalId: scheduled.id,
|
|
@@ -6598,7 +6713,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
6598
6713
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6599
6714
|
originalPriceOpen: scheduled.priceOpen,
|
|
6600
6715
|
pnl: toProfitLossDto(scheduled, averagePrice),
|
|
6601
|
-
note: scheduled.note,
|
|
6716
|
+
note: cancelNote ?? scheduled.note,
|
|
6602
6717
|
});
|
|
6603
6718
|
}
|
|
6604
6719
|
await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6765,7 +6880,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
|
|
|
6765
6880
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
6766
6881
|
originalPriceOpen: closedSignal.priceOpen,
|
|
6767
6882
|
pnl: toProfitLossDto(closedSignal, averagePrice),
|
|
6768
|
-
note: closedSignal.note,
|
|
6883
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
6769
6884
|
});
|
|
6770
6885
|
await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
6771
6886
|
await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6812,7 +6927,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6812
6927
|
if (self._cancelledSignal) {
|
|
6813
6928
|
// Сигнал был отменен через cancel() в onSchedulePing
|
|
6814
6929
|
const cancelId = self._cancelledSignal.cancelId;
|
|
6815
|
-
const
|
|
6930
|
+
const cancelNote = self._cancelledSignal.cancelNote;
|
|
6931
|
+
const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId, cancelNote);
|
|
6816
6932
|
return { outcome: "cancelled", result };
|
|
6817
6933
|
}
|
|
6818
6934
|
// КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
|
|
@@ -6866,7 +6982,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6866
6982
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
6867
6983
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
6868
6984
|
pnl: toProfitLossDto(activatedSignal, averagePrice),
|
|
6869
|
-
note: activatedSignal.note,
|
|
6985
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
6870
6986
|
});
|
|
6871
6987
|
return { outcome: "pending" };
|
|
6872
6988
|
}
|
|
@@ -6898,7 +7014,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6898
7014
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
6899
7015
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
6900
7016
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
6901
|
-
note: publicSignalForCommit.note,
|
|
7017
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
6902
7018
|
});
|
|
6903
7019
|
await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
|
|
6904
7020
|
await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
|
|
@@ -8019,6 +8135,44 @@ class ClientStrategy {
|
|
|
8019
8135
|
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8020
8136
|
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8021
8137
|
}
|
|
8138
|
+
/**
|
|
8139
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
8140
|
+
*
|
|
8141
|
+
* Measures the total swing from the stored `_peak.pnlPercentage` to the stored `_fall.pnlPercentage`.
|
|
8142
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
8143
|
+
*
|
|
8144
|
+
* Returns null if no pending signal exists.
|
|
8145
|
+
*
|
|
8146
|
+
* @param symbol - Trading pair symbol
|
|
8147
|
+
* @param currentPrice - Current market price
|
|
8148
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
8149
|
+
*/
|
|
8150
|
+
async getMaxDrawdownDistancePnlPercentage(symbol, currentPrice) {
|
|
8151
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlPercentage", { symbol, currentPrice });
|
|
8152
|
+
if (!this._pendingSignal) {
|
|
8153
|
+
return null;
|
|
8154
|
+
}
|
|
8155
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8156
|
+
}
|
|
8157
|
+
/**
|
|
8158
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
8159
|
+
*
|
|
8160
|
+
* Measures the total swing from the stored `_peak.pnlCost` to the stored `_fall.pnlCost`.
|
|
8161
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
8162
|
+
*
|
|
8163
|
+
* Returns null if no pending signal exists.
|
|
8164
|
+
*
|
|
8165
|
+
* @param symbol - Trading pair symbol
|
|
8166
|
+
* @param currentPrice - Current market price
|
|
8167
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
8168
|
+
*/
|
|
8169
|
+
async getMaxDrawdownDistancePnlCost(symbol, currentPrice) {
|
|
8170
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlCost", { symbol, currentPrice });
|
|
8171
|
+
if (!this._pendingSignal) {
|
|
8172
|
+
return null;
|
|
8173
|
+
}
|
|
8174
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8175
|
+
}
|
|
8022
8176
|
/**
|
|
8023
8177
|
* Performs a single tick of strategy execution.
|
|
8024
8178
|
*
|
|
@@ -8083,7 +8237,7 @@ class ClientStrategy {
|
|
|
8083
8237
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8084
8238
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8085
8239
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8086
|
-
note: cancelledSignal.note,
|
|
8240
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8087
8241
|
});
|
|
8088
8242
|
// Call onCancel callback
|
|
8089
8243
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8137,7 +8291,7 @@ class ClientStrategy {
|
|
|
8137
8291
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8138
8292
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8139
8293
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8140
|
-
note: closedSignal.note,
|
|
8294
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8141
8295
|
});
|
|
8142
8296
|
// Call onClose callback
|
|
8143
8297
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8219,7 +8373,7 @@ class ClientStrategy {
|
|
|
8219
8373
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
8220
8374
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
8221
8375
|
pnl: toProfitLossDto(activatedSignal, currentPrice),
|
|
8222
|
-
note: activatedSignal.note,
|
|
8376
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
8223
8377
|
});
|
|
8224
8378
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
8225
8379
|
}
|
|
@@ -8250,7 +8404,7 @@ class ClientStrategy {
|
|
|
8250
8404
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
8251
8405
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
8252
8406
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
8253
|
-
note: publicSignalForCommit.note,
|
|
8407
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
8254
8408
|
});
|
|
8255
8409
|
// Call onOpen callback
|
|
8256
8410
|
await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8386,7 +8540,7 @@ class ClientStrategy {
|
|
|
8386
8540
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8387
8541
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8388
8542
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8389
|
-
note: cancelledSignal.note,
|
|
8543
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8390
8544
|
});
|
|
8391
8545
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8392
8546
|
const cancelledResult = {
|
|
@@ -8441,7 +8595,7 @@ class ClientStrategy {
|
|
|
8441
8595
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8442
8596
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8443
8597
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8444
|
-
note: closedSignal.note,
|
|
8598
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8445
8599
|
});
|
|
8446
8600
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8447
8601
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
@@ -8625,7 +8779,8 @@ class ClientStrategy {
|
|
|
8625
8779
|
* // Strategy continues, can generate new signals
|
|
8626
8780
|
* ```
|
|
8627
8781
|
*/
|
|
8628
|
-
async cancelScheduled(symbol, backtest,
|
|
8782
|
+
async cancelScheduled(symbol, backtest, payload) {
|
|
8783
|
+
const cancelId = payload.id;
|
|
8629
8784
|
this.params.logger.debug("ClientStrategy cancelScheduled", {
|
|
8630
8785
|
symbol,
|
|
8631
8786
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8637,6 +8792,7 @@ class ClientStrategy {
|
|
|
8637
8792
|
if (this._scheduledSignal) {
|
|
8638
8793
|
this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
|
|
8639
8794
|
cancelId,
|
|
8795
|
+
cancelNote: payload.note,
|
|
8640
8796
|
});
|
|
8641
8797
|
this._scheduledSignal = null;
|
|
8642
8798
|
}
|
|
@@ -8668,7 +8824,8 @@ class ClientStrategy {
|
|
|
8668
8824
|
* // Scheduled signal becomes pending signal immediately
|
|
8669
8825
|
* ```
|
|
8670
8826
|
*/
|
|
8671
|
-
async activateScheduled(symbol, backtest,
|
|
8827
|
+
async activateScheduled(symbol, backtest, payload) {
|
|
8828
|
+
const activateId = payload.id;
|
|
8672
8829
|
this.params.logger.debug("ClientStrategy activateScheduled", {
|
|
8673
8830
|
symbol,
|
|
8674
8831
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8686,6 +8843,7 @@ class ClientStrategy {
|
|
|
8686
8843
|
if (this._scheduledSignal) {
|
|
8687
8844
|
this._activatedSignal = Object.assign({}, this._scheduledSignal, {
|
|
8688
8845
|
activateId,
|
|
8846
|
+
activateNote: payload.note,
|
|
8689
8847
|
});
|
|
8690
8848
|
this._scheduledSignal = null;
|
|
8691
8849
|
}
|
|
@@ -8717,7 +8875,8 @@ class ClientStrategy {
|
|
|
8717
8875
|
* // Strategy continues, can generate new signals
|
|
8718
8876
|
* ```
|
|
8719
8877
|
*/
|
|
8720
|
-
async closePending(symbol, backtest,
|
|
8878
|
+
async closePending(symbol, backtest, payload) {
|
|
8879
|
+
const closeId = payload.id;
|
|
8721
8880
|
this.params.logger.debug("ClientStrategy closePending", {
|
|
8722
8881
|
symbol,
|
|
8723
8882
|
hasPendingSignal: this._pendingSignal !== null,
|
|
@@ -8728,6 +8887,7 @@ class ClientStrategy {
|
|
|
8728
8887
|
if (this._pendingSignal) {
|
|
8729
8888
|
this._closedSignal = Object.assign({}, this._pendingSignal, {
|
|
8730
8889
|
closeId,
|
|
8890
|
+
closeNote: payload.note,
|
|
8731
8891
|
});
|
|
8732
8892
|
this._pendingSignal = null;
|
|
8733
8893
|
}
|
|
@@ -10110,7 +10270,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
10110
10270
|
* @param backtest - Whether running in backtest mode
|
|
10111
10271
|
* @returns Unique string key for memoization
|
|
10112
10272
|
*/
|
|
10113
|
-
const CREATE_KEY_FN$
|
|
10273
|
+
const CREATE_KEY_FN$u = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10114
10274
|
const parts = [symbol, strategyName, exchangeName];
|
|
10115
10275
|
if (frameName)
|
|
10116
10276
|
parts.push(frameName);
|
|
@@ -10377,7 +10537,7 @@ class StrategyConnectionService {
|
|
|
10377
10537
|
* @param backtest - Whether running in backtest mode
|
|
10378
10538
|
* @returns Configured ClientStrategy instance
|
|
10379
10539
|
*/
|
|
10380
|
-
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10540
|
+
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10381
10541
|
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10382
10542
|
return new ClientStrategy({
|
|
10383
10543
|
symbol,
|
|
@@ -11218,6 +11378,48 @@ class StrategyConnectionService {
|
|
|
11218
11378
|
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11219
11379
|
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11220
11380
|
};
|
|
11381
|
+
/**
|
|
11382
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
11383
|
+
*
|
|
11384
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11385
|
+
* ClientStrategy.getMaxDrawdownDistancePnlPercentage().
|
|
11386
|
+
* Returns null if no pending signal exists.
|
|
11387
|
+
*
|
|
11388
|
+
* @param backtest - Whether running in backtest mode
|
|
11389
|
+
* @param symbol - Trading pair symbol
|
|
11390
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11391
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
11392
|
+
*/
|
|
11393
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11394
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlPercentage", {
|
|
11395
|
+
symbol,
|
|
11396
|
+
context,
|
|
11397
|
+
});
|
|
11398
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11399
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11400
|
+
return await strategy.getMaxDrawdownDistancePnlPercentage(symbol, currentPrice);
|
|
11401
|
+
};
|
|
11402
|
+
/**
|
|
11403
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
11404
|
+
*
|
|
11405
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11406
|
+
* ClientStrategy.getMaxDrawdownDistancePnlCost().
|
|
11407
|
+
* Returns null if no pending signal exists.
|
|
11408
|
+
*
|
|
11409
|
+
* @param backtest - Whether running in backtest mode
|
|
11410
|
+
* @param symbol - Trading pair symbol
|
|
11411
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11412
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
11413
|
+
*/
|
|
11414
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
11415
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlCost", {
|
|
11416
|
+
symbol,
|
|
11417
|
+
context,
|
|
11418
|
+
});
|
|
11419
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11420
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11421
|
+
return await strategy.getMaxDrawdownDistancePnlCost(symbol, currentPrice);
|
|
11422
|
+
};
|
|
11221
11423
|
/**
|
|
11222
11424
|
* Disposes the ClientStrategy instance for the given context.
|
|
11223
11425
|
*
|
|
@@ -11256,7 +11458,7 @@ class StrategyConnectionService {
|
|
|
11256
11458
|
}
|
|
11257
11459
|
return;
|
|
11258
11460
|
}
|
|
11259
|
-
const key = CREATE_KEY_FN$
|
|
11461
|
+
const key = CREATE_KEY_FN$u(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
11260
11462
|
if (!this.getStrategy.has(key)) {
|
|
11261
11463
|
return;
|
|
11262
11464
|
}
|
|
@@ -11277,17 +11479,17 @@ class StrategyConnectionService {
|
|
|
11277
11479
|
* @param backtest - Whether running in backtest mode
|
|
11278
11480
|
* @param symbol - Trading pair symbol
|
|
11279
11481
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
11280
|
-
* @param
|
|
11482
|
+
* @param payload - Optional commit payload with id and note
|
|
11281
11483
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
11282
11484
|
*/
|
|
11283
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
11485
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11284
11486
|
this.loggerService.log("strategyConnectionService cancelScheduled", {
|
|
11285
11487
|
symbol,
|
|
11286
11488
|
context,
|
|
11287
|
-
|
|
11489
|
+
payload,
|
|
11288
11490
|
});
|
|
11289
11491
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11290
|
-
await strategy.cancelScheduled(symbol, backtest,
|
|
11492
|
+
await strategy.cancelScheduled(symbol, backtest, payload);
|
|
11291
11493
|
};
|
|
11292
11494
|
/**
|
|
11293
11495
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -11302,17 +11504,17 @@ class StrategyConnectionService {
|
|
|
11302
11504
|
* @param backtest - Whether running in backtest mode
|
|
11303
11505
|
* @param symbol - Trading pair symbol
|
|
11304
11506
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
11305
|
-
* @param
|
|
11507
|
+
* @param payload - Optional commit payload with id and note
|
|
11306
11508
|
* @returns Promise that resolves when pending signal is closed
|
|
11307
11509
|
*/
|
|
11308
|
-
this.closePending = async (backtest, symbol, context,
|
|
11510
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
11309
11511
|
this.loggerService.log("strategyConnectionService closePending", {
|
|
11310
11512
|
symbol,
|
|
11311
11513
|
context,
|
|
11312
|
-
|
|
11514
|
+
payload,
|
|
11313
11515
|
});
|
|
11314
11516
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11315
|
-
await strategy.closePending(symbol, backtest,
|
|
11517
|
+
await strategy.closePending(symbol, backtest, payload);
|
|
11316
11518
|
};
|
|
11317
11519
|
/**
|
|
11318
11520
|
* Checks whether `partialProfit` would succeed without executing it.
|
|
@@ -11591,7 +11793,7 @@ class StrategyConnectionService {
|
|
|
11591
11793
|
* @param backtest - Whether running in backtest mode
|
|
11592
11794
|
* @param symbol - Trading pair symbol
|
|
11593
11795
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11594
|
-
* @param
|
|
11796
|
+
* @param payload - Optional commit payload with id and note
|
|
11595
11797
|
* @returns Promise that resolves when activation flag is set
|
|
11596
11798
|
*
|
|
11597
11799
|
* @example
|
|
@@ -11601,19 +11803,19 @@ class StrategyConnectionService {
|
|
|
11601
11803
|
* false,
|
|
11602
11804
|
* "BTCUSDT",
|
|
11603
11805
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
11604
|
-
* "manual-activation"
|
|
11806
|
+
* { id: "manual-activation" }
|
|
11605
11807
|
* );
|
|
11606
11808
|
* ```
|
|
11607
11809
|
*/
|
|
11608
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
11810
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11609
11811
|
this.loggerService.log("strategyConnectionService activateScheduled", {
|
|
11610
11812
|
symbol,
|
|
11611
11813
|
context,
|
|
11612
11814
|
backtest,
|
|
11613
|
-
|
|
11815
|
+
payload,
|
|
11614
11816
|
});
|
|
11615
11817
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11616
|
-
return await strategy.activateScheduled(symbol, backtest,
|
|
11818
|
+
return await strategy.activateScheduled(symbol, backtest, payload);
|
|
11617
11819
|
};
|
|
11618
11820
|
/**
|
|
11619
11821
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -12425,7 +12627,7 @@ class ClientRisk {
|
|
|
12425
12627
|
* @param backtest - Whether running in backtest mode
|
|
12426
12628
|
* @returns Unique string key for memoization
|
|
12427
12629
|
*/
|
|
12428
|
-
const CREATE_KEY_FN$
|
|
12630
|
+
const CREATE_KEY_FN$t = (riskName, exchangeName, frameName, backtest) => {
|
|
12429
12631
|
const parts = [riskName, exchangeName];
|
|
12430
12632
|
if (frameName)
|
|
12431
12633
|
parts.push(frameName);
|
|
@@ -12525,7 +12727,7 @@ class RiskConnectionService {
|
|
|
12525
12727
|
* @param backtest - True if backtest mode, false if live mode
|
|
12526
12728
|
* @returns Configured ClientRisk instance
|
|
12527
12729
|
*/
|
|
12528
|
-
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12730
|
+
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
12529
12731
|
const schema = this.riskSchemaService.get(riskName);
|
|
12530
12732
|
return new ClientRisk({
|
|
12531
12733
|
...schema,
|
|
@@ -12594,7 +12796,7 @@ class RiskConnectionService {
|
|
|
12594
12796
|
payload,
|
|
12595
12797
|
});
|
|
12596
12798
|
if (payload) {
|
|
12597
|
-
const key = CREATE_KEY_FN$
|
|
12799
|
+
const key = CREATE_KEY_FN$t(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12598
12800
|
this.getRisk.clear(key);
|
|
12599
12801
|
}
|
|
12600
12802
|
else {
|
|
@@ -13638,7 +13840,7 @@ class ClientAction {
|
|
|
13638
13840
|
* @param backtest - Whether running in backtest mode
|
|
13639
13841
|
* @returns Unique string key for memoization
|
|
13640
13842
|
*/
|
|
13641
|
-
const CREATE_KEY_FN$
|
|
13843
|
+
const CREATE_KEY_FN$s = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13642
13844
|
const parts = [actionName, strategyName, exchangeName];
|
|
13643
13845
|
if (frameName)
|
|
13644
13846
|
parts.push(frameName);
|
|
@@ -13690,7 +13892,7 @@ class ActionConnectionService {
|
|
|
13690
13892
|
* @param backtest - True if backtest mode, false if live mode
|
|
13691
13893
|
* @returns Configured ClientAction instance
|
|
13692
13894
|
*/
|
|
13693
|
-
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
13895
|
+
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13694
13896
|
const schema = this.actionSchemaService.get(actionName);
|
|
13695
13897
|
return new ClientAction({
|
|
13696
13898
|
...schema,
|
|
@@ -13901,7 +14103,7 @@ class ActionConnectionService {
|
|
|
13901
14103
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
13902
14104
|
return;
|
|
13903
14105
|
}
|
|
13904
|
-
const key = CREATE_KEY_FN$
|
|
14106
|
+
const key = CREATE_KEY_FN$s(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13905
14107
|
if (!this.getAction.has(key)) {
|
|
13906
14108
|
return;
|
|
13907
14109
|
}
|
|
@@ -13919,7 +14121,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
13919
14121
|
* @param exchangeName - Exchange name
|
|
13920
14122
|
* @returns Unique string key for memoization
|
|
13921
14123
|
*/
|
|
13922
|
-
const CREATE_KEY_FN$
|
|
14124
|
+
const CREATE_KEY_FN$r = (exchangeName) => {
|
|
13923
14125
|
return exchangeName;
|
|
13924
14126
|
};
|
|
13925
14127
|
/**
|
|
@@ -13943,7 +14145,7 @@ class ExchangeCoreService {
|
|
|
13943
14145
|
* @param exchangeName - Name of the exchange to validate
|
|
13944
14146
|
* @returns Promise that resolves when validation is complete
|
|
13945
14147
|
*/
|
|
13946
|
-
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
14148
|
+
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$r(exchangeName), async (exchangeName) => {
|
|
13947
14149
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
13948
14150
|
exchangeName,
|
|
13949
14151
|
});
|
|
@@ -14195,7 +14397,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
14195
14397
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14196
14398
|
* @returns Unique string key for memoization
|
|
14197
14399
|
*/
|
|
14198
|
-
const CREATE_KEY_FN$
|
|
14400
|
+
const CREATE_KEY_FN$q = (context) => {
|
|
14199
14401
|
const parts = [context.strategyName, context.exchangeName];
|
|
14200
14402
|
if (context.frameName)
|
|
14201
14403
|
parts.push(context.frameName);
|
|
@@ -14227,7 +14429,7 @@ class StrategyCoreService {
|
|
|
14227
14429
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14228
14430
|
* @returns Promise that resolves when validation is complete
|
|
14229
14431
|
*/
|
|
14230
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
14432
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
|
|
14231
14433
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
14232
14434
|
context,
|
|
14233
14435
|
});
|
|
@@ -14648,18 +14850,18 @@ class StrategyCoreService {
|
|
|
14648
14850
|
* @param backtest - Whether running in backtest mode
|
|
14649
14851
|
* @param symbol - Trading pair symbol
|
|
14650
14852
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
14651
|
-
* @param
|
|
14853
|
+
* @param payload - Optional commit payload with id and note
|
|
14652
14854
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
14653
14855
|
*/
|
|
14654
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
14856
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
14655
14857
|
this.loggerService.log("strategyCoreService cancelScheduled", {
|
|
14656
14858
|
symbol,
|
|
14657
14859
|
context,
|
|
14658
14860
|
backtest,
|
|
14659
|
-
|
|
14861
|
+
payload,
|
|
14660
14862
|
});
|
|
14661
14863
|
await this.validate(context);
|
|
14662
|
-
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context,
|
|
14864
|
+
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, payload);
|
|
14663
14865
|
};
|
|
14664
14866
|
/**
|
|
14665
14867
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -14675,18 +14877,18 @@ class StrategyCoreService {
|
|
|
14675
14877
|
* @param backtest - Whether running in backtest mode
|
|
14676
14878
|
* @param symbol - Trading pair symbol
|
|
14677
14879
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
14678
|
-
* @param
|
|
14880
|
+
* @param payload - Optional commit payload with id and note
|
|
14679
14881
|
* @returns Promise that resolves when pending signal is closed
|
|
14680
14882
|
*/
|
|
14681
|
-
this.closePending = async (backtest, symbol, context,
|
|
14883
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
14682
14884
|
this.loggerService.log("strategyCoreService closePending", {
|
|
14683
14885
|
symbol,
|
|
14684
14886
|
context,
|
|
14685
14887
|
backtest,
|
|
14686
|
-
|
|
14888
|
+
payload,
|
|
14687
14889
|
});
|
|
14688
14890
|
await this.validate(context);
|
|
14689
|
-
return await this.strategyConnectionService.closePending(backtest, symbol, context,
|
|
14891
|
+
return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
|
|
14690
14892
|
};
|
|
14691
14893
|
/**
|
|
14692
14894
|
* Disposes the ClientStrategy instance for the given context.
|
|
@@ -15031,7 +15233,7 @@ class StrategyCoreService {
|
|
|
15031
15233
|
* @param backtest - Whether running in backtest mode
|
|
15032
15234
|
* @param symbol - Trading pair symbol
|
|
15033
15235
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15034
|
-
* @param
|
|
15236
|
+
* @param payload - Optional commit payload with id and note
|
|
15035
15237
|
* @returns Promise that resolves when activation flag is set
|
|
15036
15238
|
*
|
|
15037
15239
|
* @example
|
|
@@ -15041,19 +15243,19 @@ class StrategyCoreService {
|
|
|
15041
15243
|
* false,
|
|
15042
15244
|
* "BTCUSDT",
|
|
15043
15245
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
15044
|
-
* "manual-activation"
|
|
15246
|
+
* { id: "manual-activation" }
|
|
15045
15247
|
* );
|
|
15046
15248
|
* ```
|
|
15047
15249
|
*/
|
|
15048
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
15250
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
15049
15251
|
this.loggerService.log("strategyCoreService activateScheduled", {
|
|
15050
15252
|
symbol,
|
|
15051
15253
|
context,
|
|
15052
15254
|
backtest,
|
|
15053
|
-
|
|
15255
|
+
payload,
|
|
15054
15256
|
});
|
|
15055
15257
|
await this.validate(context);
|
|
15056
|
-
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context,
|
|
15258
|
+
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, payload);
|
|
15057
15259
|
};
|
|
15058
15260
|
/**
|
|
15059
15261
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -15455,6 +15657,44 @@ class StrategyCoreService {
|
|
|
15455
15657
|
await this.validate(context);
|
|
15456
15658
|
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15457
15659
|
};
|
|
15660
|
+
/**
|
|
15661
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
15662
|
+
*
|
|
15663
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlPercentage().
|
|
15664
|
+
* Returns null if no pending signal exists.
|
|
15665
|
+
*
|
|
15666
|
+
* @param backtest - Whether running in backtest mode
|
|
15667
|
+
* @param symbol - Trading pair symbol
|
|
15668
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15669
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
15670
|
+
*/
|
|
15671
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15672
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlPercentage", {
|
|
15673
|
+
symbol,
|
|
15674
|
+
context,
|
|
15675
|
+
});
|
|
15676
|
+
await this.validate(context);
|
|
15677
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlPercentage(backtest, symbol, context);
|
|
15678
|
+
};
|
|
15679
|
+
/**
|
|
15680
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
15681
|
+
*
|
|
15682
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlCost().
|
|
15683
|
+
* Returns null if no pending signal exists.
|
|
15684
|
+
*
|
|
15685
|
+
* @param backtest - Whether running in backtest mode
|
|
15686
|
+
* @param symbol - Trading pair symbol
|
|
15687
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15688
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
15689
|
+
*/
|
|
15690
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
15691
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlCost", {
|
|
15692
|
+
symbol,
|
|
15693
|
+
context,
|
|
15694
|
+
});
|
|
15695
|
+
await this.validate(context);
|
|
15696
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlCost(backtest, symbol, context);
|
|
15697
|
+
};
|
|
15458
15698
|
}
|
|
15459
15699
|
}
|
|
15460
15700
|
|
|
@@ -15527,7 +15767,7 @@ class SizingGlobalService {
|
|
|
15527
15767
|
* @param context - Context with riskName, exchangeName, frameName
|
|
15528
15768
|
* @returns Unique string key for memoization
|
|
15529
15769
|
*/
|
|
15530
|
-
const CREATE_KEY_FN$
|
|
15770
|
+
const CREATE_KEY_FN$p = (context) => {
|
|
15531
15771
|
const parts = [context.riskName, context.exchangeName];
|
|
15532
15772
|
if (context.frameName)
|
|
15533
15773
|
parts.push(context.frameName);
|
|
@@ -15553,7 +15793,7 @@ class RiskGlobalService {
|
|
|
15553
15793
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
15554
15794
|
* @returns Promise that resolves when validation is complete
|
|
15555
15795
|
*/
|
|
15556
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15796
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
|
|
15557
15797
|
this.loggerService.log("riskGlobalService validate", {
|
|
15558
15798
|
context,
|
|
15559
15799
|
});
|
|
@@ -15631,7 +15871,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
15631
15871
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15632
15872
|
* @returns Unique string key for memoization
|
|
15633
15873
|
*/
|
|
15634
|
-
const CREATE_KEY_FN$
|
|
15874
|
+
const CREATE_KEY_FN$o = (context) => {
|
|
15635
15875
|
const parts = [context.strategyName, context.exchangeName];
|
|
15636
15876
|
if (context.frameName)
|
|
15637
15877
|
parts.push(context.frameName);
|
|
@@ -15675,7 +15915,7 @@ class ActionCoreService {
|
|
|
15675
15915
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
15676
15916
|
* @returns Promise that resolves when all validations complete
|
|
15677
15917
|
*/
|
|
15678
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15918
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
|
|
15679
15919
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
15680
15920
|
context,
|
|
15681
15921
|
});
|
|
@@ -20718,7 +20958,7 @@ const ReportWriter = new ReportWriterAdapter();
|
|
|
20718
20958
|
* @param backtest - Whether running in backtest mode
|
|
20719
20959
|
* @returns Unique string key for memoization
|
|
20720
20960
|
*/
|
|
20721
|
-
const CREATE_KEY_FN$
|
|
20961
|
+
const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20722
20962
|
const parts = [symbol, strategyName, exchangeName];
|
|
20723
20963
|
if (frameName)
|
|
20724
20964
|
parts.push(frameName);
|
|
@@ -20964,7 +21204,7 @@ class BacktestMarkdownService {
|
|
|
20964
21204
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20965
21205
|
* Each combination gets its own isolated storage instance.
|
|
20966
21206
|
*/
|
|
20967
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21207
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
|
|
20968
21208
|
/**
|
|
20969
21209
|
* Processes tick events and accumulates closed signals.
|
|
20970
21210
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -21121,7 +21361,7 @@ class BacktestMarkdownService {
|
|
|
21121
21361
|
payload,
|
|
21122
21362
|
});
|
|
21123
21363
|
if (payload) {
|
|
21124
|
-
const key = CREATE_KEY_FN$
|
|
21364
|
+
const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21125
21365
|
this.getStorage.clear(key);
|
|
21126
21366
|
}
|
|
21127
21367
|
else {
|
|
@@ -21183,7 +21423,7 @@ class BacktestMarkdownService {
|
|
|
21183
21423
|
* @param backtest - Whether running in backtest mode
|
|
21184
21424
|
* @returns Unique string key for memoization
|
|
21185
21425
|
*/
|
|
21186
|
-
const CREATE_KEY_FN$
|
|
21426
|
+
const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21187
21427
|
const parts = [symbol, strategyName, exchangeName];
|
|
21188
21428
|
if (frameName)
|
|
21189
21429
|
parts.push(frameName);
|
|
@@ -21678,7 +21918,7 @@ class LiveMarkdownService {
|
|
|
21678
21918
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21679
21919
|
* Each combination gets its own isolated storage instance.
|
|
21680
21920
|
*/
|
|
21681
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21921
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
|
|
21682
21922
|
/**
|
|
21683
21923
|
* Subscribes to live signal emitter to receive tick events.
|
|
21684
21924
|
* Protected against multiple subscriptions.
|
|
@@ -21896,7 +22136,7 @@ class LiveMarkdownService {
|
|
|
21896
22136
|
payload,
|
|
21897
22137
|
});
|
|
21898
22138
|
if (payload) {
|
|
21899
|
-
const key = CREATE_KEY_FN$
|
|
22139
|
+
const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21900
22140
|
this.getStorage.clear(key);
|
|
21901
22141
|
}
|
|
21902
22142
|
else {
|
|
@@ -21916,7 +22156,7 @@ class LiveMarkdownService {
|
|
|
21916
22156
|
* @param backtest - Whether running in backtest mode
|
|
21917
22157
|
* @returns Unique string key for memoization
|
|
21918
22158
|
*/
|
|
21919
|
-
const CREATE_KEY_FN$
|
|
22159
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21920
22160
|
const parts = [symbol, strategyName, exchangeName];
|
|
21921
22161
|
if (frameName)
|
|
21922
22162
|
parts.push(frameName);
|
|
@@ -22205,7 +22445,7 @@ class ScheduleMarkdownService {
|
|
|
22205
22445
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22206
22446
|
* Each combination gets its own isolated storage instance.
|
|
22207
22447
|
*/
|
|
22208
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22448
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
22209
22449
|
/**
|
|
22210
22450
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
22211
22451
|
* Protected against multiple subscriptions.
|
|
@@ -22408,7 +22648,7 @@ class ScheduleMarkdownService {
|
|
|
22408
22648
|
payload,
|
|
22409
22649
|
});
|
|
22410
22650
|
if (payload) {
|
|
22411
|
-
const key = CREATE_KEY_FN$
|
|
22651
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22412
22652
|
this.getStorage.clear(key);
|
|
22413
22653
|
}
|
|
22414
22654
|
else {
|
|
@@ -22428,7 +22668,7 @@ class ScheduleMarkdownService {
|
|
|
22428
22668
|
* @param backtest - Whether running in backtest mode
|
|
22429
22669
|
* @returns Unique string key for memoization
|
|
22430
22670
|
*/
|
|
22431
|
-
const CREATE_KEY_FN$
|
|
22671
|
+
const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
22432
22672
|
const parts = [symbol, strategyName, exchangeName];
|
|
22433
22673
|
if (frameName)
|
|
22434
22674
|
parts.push(frameName);
|
|
@@ -22673,7 +22913,7 @@ class PerformanceMarkdownService {
|
|
|
22673
22913
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22674
22914
|
* Each combination gets its own isolated storage instance.
|
|
22675
22915
|
*/
|
|
22676
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22916
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
22677
22917
|
/**
|
|
22678
22918
|
* Subscribes to performance emitter to receive performance events.
|
|
22679
22919
|
* Protected against multiple subscriptions.
|
|
@@ -22840,7 +23080,7 @@ class PerformanceMarkdownService {
|
|
|
22840
23080
|
payload,
|
|
22841
23081
|
});
|
|
22842
23082
|
if (payload) {
|
|
22843
|
-
const key = CREATE_KEY_FN$
|
|
23083
|
+
const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22844
23084
|
this.getStorage.clear(key);
|
|
22845
23085
|
}
|
|
22846
23086
|
else {
|
|
@@ -23319,7 +23559,7 @@ class WalkerMarkdownService {
|
|
|
23319
23559
|
* @param backtest - Whether running in backtest mode
|
|
23320
23560
|
* @returns Unique string key for memoization
|
|
23321
23561
|
*/
|
|
23322
|
-
const CREATE_KEY_FN$
|
|
23562
|
+
const CREATE_KEY_FN$j = (exchangeName, frameName, backtest) => {
|
|
23323
23563
|
const parts = [exchangeName];
|
|
23324
23564
|
if (frameName)
|
|
23325
23565
|
parts.push(frameName);
|
|
@@ -23766,7 +24006,7 @@ class HeatMarkdownService {
|
|
|
23766
24006
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
23767
24007
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
23768
24008
|
*/
|
|
23769
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24009
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
23770
24010
|
/**
|
|
23771
24011
|
* Subscribes to signal emitter to receive tick events.
|
|
23772
24012
|
* Protected against multiple subscriptions.
|
|
@@ -23984,7 +24224,7 @@ class HeatMarkdownService {
|
|
|
23984
24224
|
payload,
|
|
23985
24225
|
});
|
|
23986
24226
|
if (payload) {
|
|
23987
|
-
const key = CREATE_KEY_FN$
|
|
24227
|
+
const key = CREATE_KEY_FN$j(payload.exchangeName, payload.frameName, payload.backtest);
|
|
23988
24228
|
this.getStorage.clear(key);
|
|
23989
24229
|
}
|
|
23990
24230
|
else {
|
|
@@ -25015,7 +25255,7 @@ class ClientPartial {
|
|
|
25015
25255
|
* @param backtest - Whether running in backtest mode
|
|
25016
25256
|
* @returns Unique string key for memoization
|
|
25017
25257
|
*/
|
|
25018
|
-
const CREATE_KEY_FN$
|
|
25258
|
+
const CREATE_KEY_FN$i = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
25019
25259
|
/**
|
|
25020
25260
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
25021
25261
|
*
|
|
@@ -25137,7 +25377,7 @@ class PartialConnectionService {
|
|
|
25137
25377
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
25138
25378
|
* Value: ClientPartial instance with logger and event emitters
|
|
25139
25379
|
*/
|
|
25140
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
25380
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$i(signalId, backtest), (signalId, backtest) => {
|
|
25141
25381
|
return new ClientPartial({
|
|
25142
25382
|
signalId,
|
|
25143
25383
|
logger: this.loggerService,
|
|
@@ -25227,7 +25467,7 @@ class PartialConnectionService {
|
|
|
25227
25467
|
const partial = this.getPartial(data.id, backtest);
|
|
25228
25468
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
25229
25469
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
25230
|
-
const key = CREATE_KEY_FN$
|
|
25470
|
+
const key = CREATE_KEY_FN$i(data.id, backtest);
|
|
25231
25471
|
this.getPartial.clear(key);
|
|
25232
25472
|
};
|
|
25233
25473
|
}
|
|
@@ -25243,7 +25483,7 @@ class PartialConnectionService {
|
|
|
25243
25483
|
* @param backtest - Whether running in backtest mode
|
|
25244
25484
|
* @returns Unique string key for memoization
|
|
25245
25485
|
*/
|
|
25246
|
-
const CREATE_KEY_FN$
|
|
25486
|
+
const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
25247
25487
|
const parts = [symbol, strategyName, exchangeName];
|
|
25248
25488
|
if (frameName)
|
|
25249
25489
|
parts.push(frameName);
|
|
@@ -25466,7 +25706,7 @@ class PartialMarkdownService {
|
|
|
25466
25706
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
25467
25707
|
* Each combination gets its own isolated storage instance.
|
|
25468
25708
|
*/
|
|
25469
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25709
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
25470
25710
|
/**
|
|
25471
25711
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
25472
25712
|
* Protected against multiple subscriptions.
|
|
@@ -25676,7 +25916,7 @@ class PartialMarkdownService {
|
|
|
25676
25916
|
payload,
|
|
25677
25917
|
});
|
|
25678
25918
|
if (payload) {
|
|
25679
|
-
const key = CREATE_KEY_FN$
|
|
25919
|
+
const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25680
25920
|
this.getStorage.clear(key);
|
|
25681
25921
|
}
|
|
25682
25922
|
else {
|
|
@@ -25692,7 +25932,7 @@ class PartialMarkdownService {
|
|
|
25692
25932
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
25693
25933
|
* @returns Unique string key for memoization
|
|
25694
25934
|
*/
|
|
25695
|
-
const CREATE_KEY_FN$
|
|
25935
|
+
const CREATE_KEY_FN$g = (context) => {
|
|
25696
25936
|
const parts = [context.strategyName, context.exchangeName];
|
|
25697
25937
|
if (context.frameName)
|
|
25698
25938
|
parts.push(context.frameName);
|
|
@@ -25766,7 +26006,7 @@ class PartialGlobalService {
|
|
|
25766
26006
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
25767
26007
|
* @param methodName - Name of the calling method for error tracking
|
|
25768
26008
|
*/
|
|
25769
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
26009
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), (context, methodName) => {
|
|
25770
26010
|
this.loggerService.log("partialGlobalService validate", {
|
|
25771
26011
|
context,
|
|
25772
26012
|
methodName,
|
|
@@ -26221,7 +26461,7 @@ class ClientBreakeven {
|
|
|
26221
26461
|
* @param backtest - Whether running in backtest mode
|
|
26222
26462
|
* @returns Unique string key for memoization
|
|
26223
26463
|
*/
|
|
26224
|
-
const CREATE_KEY_FN$
|
|
26464
|
+
const CREATE_KEY_FN$f = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
26225
26465
|
/**
|
|
26226
26466
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
26227
26467
|
*
|
|
@@ -26307,7 +26547,7 @@ class BreakevenConnectionService {
|
|
|
26307
26547
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
26308
26548
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
26309
26549
|
*/
|
|
26310
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
26550
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$f(signalId, backtest), (signalId, backtest) => {
|
|
26311
26551
|
return new ClientBreakeven({
|
|
26312
26552
|
signalId,
|
|
26313
26553
|
logger: this.loggerService,
|
|
@@ -26368,7 +26608,7 @@ class BreakevenConnectionService {
|
|
|
26368
26608
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
26369
26609
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
26370
26610
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
26371
|
-
const key = CREATE_KEY_FN$
|
|
26611
|
+
const key = CREATE_KEY_FN$f(data.id, backtest);
|
|
26372
26612
|
this.getBreakeven.clear(key);
|
|
26373
26613
|
};
|
|
26374
26614
|
}
|
|
@@ -26384,7 +26624,7 @@ class BreakevenConnectionService {
|
|
|
26384
26624
|
* @param backtest - Whether running in backtest mode
|
|
26385
26625
|
* @returns Unique string key for memoization
|
|
26386
26626
|
*/
|
|
26387
|
-
const CREATE_KEY_FN$
|
|
26627
|
+
const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
26388
26628
|
const parts = [symbol, strategyName, exchangeName];
|
|
26389
26629
|
if (frameName)
|
|
26390
26630
|
parts.push(frameName);
|
|
@@ -26559,7 +26799,7 @@ class BreakevenMarkdownService {
|
|
|
26559
26799
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26560
26800
|
* Each combination gets its own isolated storage instance.
|
|
26561
26801
|
*/
|
|
26562
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26802
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
26563
26803
|
/**
|
|
26564
26804
|
* Subscribes to breakeven signal emitter to receive events.
|
|
26565
26805
|
* Protected against multiple subscriptions.
|
|
@@ -26748,7 +26988,7 @@ class BreakevenMarkdownService {
|
|
|
26748
26988
|
payload,
|
|
26749
26989
|
});
|
|
26750
26990
|
if (payload) {
|
|
26751
|
-
const key = CREATE_KEY_FN$
|
|
26991
|
+
const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26752
26992
|
this.getStorage.clear(key);
|
|
26753
26993
|
}
|
|
26754
26994
|
else {
|
|
@@ -26764,7 +27004,7 @@ class BreakevenMarkdownService {
|
|
|
26764
27004
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
26765
27005
|
* @returns Unique string key for memoization
|
|
26766
27006
|
*/
|
|
26767
|
-
const CREATE_KEY_FN$
|
|
27007
|
+
const CREATE_KEY_FN$d = (context) => {
|
|
26768
27008
|
const parts = [context.strategyName, context.exchangeName];
|
|
26769
27009
|
if (context.frameName)
|
|
26770
27010
|
parts.push(context.frameName);
|
|
@@ -26838,7 +27078,7 @@ class BreakevenGlobalService {
|
|
|
26838
27078
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
26839
27079
|
* @param methodName - Name of the calling method for error tracking
|
|
26840
27080
|
*/
|
|
26841
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
27081
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$d(context), (context, methodName) => {
|
|
26842
27082
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
26843
27083
|
context,
|
|
26844
27084
|
methodName,
|
|
@@ -27059,7 +27299,7 @@ class ConfigValidationService {
|
|
|
27059
27299
|
* @param backtest - Whether running in backtest mode
|
|
27060
27300
|
* @returns Unique string key for memoization
|
|
27061
27301
|
*/
|
|
27062
|
-
const CREATE_KEY_FN$
|
|
27302
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
27063
27303
|
const parts = [symbol, strategyName, exchangeName];
|
|
27064
27304
|
if (frameName)
|
|
27065
27305
|
parts.push(frameName);
|
|
@@ -27226,7 +27466,7 @@ class RiskMarkdownService {
|
|
|
27226
27466
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
27227
27467
|
* Each combination gets its own isolated storage instance.
|
|
27228
27468
|
*/
|
|
27229
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
27469
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
27230
27470
|
/**
|
|
27231
27471
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
27232
27472
|
* Protected against multiple subscriptions.
|
|
@@ -27415,7 +27655,7 @@ class RiskMarkdownService {
|
|
|
27415
27655
|
payload,
|
|
27416
27656
|
});
|
|
27417
27657
|
if (payload) {
|
|
27418
|
-
const key = CREATE_KEY_FN$
|
|
27658
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
27419
27659
|
this.getStorage.clear(key);
|
|
27420
27660
|
}
|
|
27421
27661
|
else {
|
|
@@ -29054,7 +29294,7 @@ class StrategyReportService {
|
|
|
29054
29294
|
/**
|
|
29055
29295
|
* Logs a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29056
29296
|
*/
|
|
29057
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId) => {
|
|
29297
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId, note) => {
|
|
29058
29298
|
this.loggerService.log("strategyReportService cancelScheduled", {
|
|
29059
29299
|
symbol,
|
|
29060
29300
|
isBacktest,
|
|
@@ -29067,6 +29307,7 @@ class StrategyReportService {
|
|
|
29067
29307
|
await ReportWriter.writeData("strategy", {
|
|
29068
29308
|
action: "cancel-scheduled",
|
|
29069
29309
|
cancelId,
|
|
29310
|
+
note,
|
|
29070
29311
|
symbol,
|
|
29071
29312
|
timestamp,
|
|
29072
29313
|
createdAt,
|
|
@@ -29088,7 +29329,7 @@ class StrategyReportService {
|
|
|
29088
29329
|
/**
|
|
29089
29330
|
* Logs a close-pending event when a pending signal is closed.
|
|
29090
29331
|
*/
|
|
29091
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId) => {
|
|
29332
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId, note) => {
|
|
29092
29333
|
this.loggerService.log("strategyReportService closePending", {
|
|
29093
29334
|
symbol,
|
|
29094
29335
|
isBacktest,
|
|
@@ -29101,6 +29342,7 @@ class StrategyReportService {
|
|
|
29101
29342
|
await ReportWriter.writeData("strategy", {
|
|
29102
29343
|
action: "close-pending",
|
|
29103
29344
|
closeId,
|
|
29345
|
+
note,
|
|
29104
29346
|
symbol,
|
|
29105
29347
|
timestamp,
|
|
29106
29348
|
createdAt,
|
|
@@ -29350,7 +29592,7 @@ class StrategyReportService {
|
|
|
29350
29592
|
/**
|
|
29351
29593
|
* Logs an activate-scheduled event when a scheduled signal is activated early.
|
|
29352
29594
|
*/
|
|
29353
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
29595
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
29354
29596
|
this.loggerService.log("strategyReportService activateScheduled", {
|
|
29355
29597
|
symbol,
|
|
29356
29598
|
currentPrice,
|
|
@@ -29364,6 +29606,7 @@ class StrategyReportService {
|
|
|
29364
29606
|
await ReportWriter.writeData("strategy", {
|
|
29365
29607
|
action: "activate-scheduled",
|
|
29366
29608
|
activateId,
|
|
29609
|
+
note,
|
|
29367
29610
|
currentPrice,
|
|
29368
29611
|
symbol,
|
|
29369
29612
|
timestamp,
|
|
@@ -29457,14 +29700,14 @@ class StrategyReportService {
|
|
|
29457
29700
|
exchangeName: event.exchangeName,
|
|
29458
29701
|
frameName: event.frameName,
|
|
29459
29702
|
strategyName: event.strategyName,
|
|
29460
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId));
|
|
29703
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId, event.note));
|
|
29461
29704
|
const unClosePending = strategyCommitSubject
|
|
29462
29705
|
.filter(({ action }) => action === "close-pending")
|
|
29463
29706
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
29464
29707
|
exchangeName: event.exchangeName,
|
|
29465
29708
|
frameName: event.frameName,
|
|
29466
29709
|
strategyName: event.strategyName,
|
|
29467
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId));
|
|
29710
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId, event.note));
|
|
29468
29711
|
const unPartialProfit = strategyCommitSubject
|
|
29469
29712
|
.filter(({ action }) => action === "partial-profit")
|
|
29470
29713
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -29506,7 +29749,7 @@ class StrategyReportService {
|
|
|
29506
29749
|
exchangeName: event.exchangeName,
|
|
29507
29750
|
frameName: event.frameName,
|
|
29508
29751
|
strategyName: event.strategyName,
|
|
29509
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
|
|
29752
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
|
|
29510
29753
|
const unAverageBuy = strategyCommitSubject
|
|
29511
29754
|
.filter(({ action }) => action === "average-buy")
|
|
29512
29755
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -29794,7 +30037,7 @@ class HighestProfitReportService {
|
|
|
29794
30037
|
* @returns Colon-separated key string for memoization
|
|
29795
30038
|
* @internal
|
|
29796
30039
|
*/
|
|
29797
|
-
const CREATE_KEY_FN$
|
|
30040
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29798
30041
|
const parts = [symbol, strategyName, exchangeName];
|
|
29799
30042
|
if (frameName)
|
|
29800
30043
|
parts.push(frameName);
|
|
@@ -30036,7 +30279,7 @@ class StrategyMarkdownService {
|
|
|
30036
30279
|
*
|
|
30037
30280
|
* @internal
|
|
30038
30281
|
*/
|
|
30039
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30282
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
30040
30283
|
/**
|
|
30041
30284
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
30042
30285
|
*
|
|
@@ -30045,8 +30288,9 @@ class StrategyMarkdownService {
|
|
|
30045
30288
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30046
30289
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30047
30290
|
* @param cancelId - Optional identifier for the cancellation reason
|
|
30291
|
+
* @param note - Optional note from commit payload
|
|
30048
30292
|
*/
|
|
30049
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId) => {
|
|
30293
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId, note) => {
|
|
30050
30294
|
this.loggerService.log("strategyMarkdownService cancelScheduled", {
|
|
30051
30295
|
symbol,
|
|
30052
30296
|
isBacktest,
|
|
@@ -30067,6 +30311,7 @@ class StrategyMarkdownService {
|
|
|
30067
30311
|
action: "cancel-scheduled",
|
|
30068
30312
|
pnl,
|
|
30069
30313
|
cancelId,
|
|
30314
|
+
note,
|
|
30070
30315
|
createdAt,
|
|
30071
30316
|
backtest: isBacktest,
|
|
30072
30317
|
});
|
|
@@ -30079,8 +30324,9 @@ class StrategyMarkdownService {
|
|
|
30079
30324
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30080
30325
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30081
30326
|
* @param closeId - Optional identifier for the close reason
|
|
30327
|
+
* @param note - Optional note from commit payload
|
|
30082
30328
|
*/
|
|
30083
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId) => {
|
|
30329
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId, note) => {
|
|
30084
30330
|
this.loggerService.log("strategyMarkdownService closePending", {
|
|
30085
30331
|
symbol,
|
|
30086
30332
|
isBacktest,
|
|
@@ -30101,6 +30347,7 @@ class StrategyMarkdownService {
|
|
|
30101
30347
|
action: "close-pending",
|
|
30102
30348
|
pnl,
|
|
30103
30349
|
closeId,
|
|
30350
|
+
note,
|
|
30104
30351
|
createdAt,
|
|
30105
30352
|
backtest: isBacktest,
|
|
30106
30353
|
});
|
|
@@ -30399,8 +30646,9 @@ class StrategyMarkdownService {
|
|
|
30399
30646
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
30400
30647
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
30401
30648
|
* @param activateId - Optional identifier for the activation reason
|
|
30649
|
+
* @param note - Optional note from commit payload
|
|
30402
30650
|
*/
|
|
30403
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
30651
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
30404
30652
|
this.loggerService.log("strategyMarkdownService activateScheduled", {
|
|
30405
30653
|
symbol,
|
|
30406
30654
|
currentPrice,
|
|
@@ -30423,6 +30671,7 @@ class StrategyMarkdownService {
|
|
|
30423
30671
|
pnl,
|
|
30424
30672
|
totalPartials,
|
|
30425
30673
|
activateId,
|
|
30674
|
+
note,
|
|
30426
30675
|
currentPrice,
|
|
30427
30676
|
createdAt,
|
|
30428
30677
|
backtest: isBacktest,
|
|
@@ -30604,7 +30853,7 @@ class StrategyMarkdownService {
|
|
|
30604
30853
|
this.clear = async (payload) => {
|
|
30605
30854
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
30606
30855
|
if (payload) {
|
|
30607
|
-
const key = CREATE_KEY_FN$
|
|
30856
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30608
30857
|
this.getStorage.clear(key);
|
|
30609
30858
|
}
|
|
30610
30859
|
else {
|
|
@@ -30627,14 +30876,14 @@ class StrategyMarkdownService {
|
|
|
30627
30876
|
exchangeName: event.exchangeName,
|
|
30628
30877
|
frameName: event.frameName,
|
|
30629
30878
|
strategyName: event.strategyName,
|
|
30630
|
-
}, event.timestamp, event.signalId, event.pnl, event.cancelId));
|
|
30879
|
+
}, event.timestamp, event.signalId, event.pnl, event.cancelId, event.note));
|
|
30631
30880
|
const unClosePending = strategyCommitSubject
|
|
30632
30881
|
.filter(({ action }) => action === "close-pending")
|
|
30633
30882
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
30634
30883
|
exchangeName: event.exchangeName,
|
|
30635
30884
|
frameName: event.frameName,
|
|
30636
30885
|
strategyName: event.strategyName,
|
|
30637
|
-
}, event.timestamp, event.signalId, event.pnl, event.closeId));
|
|
30886
|
+
}, event.timestamp, event.signalId, event.pnl, event.closeId, event.note));
|
|
30638
30887
|
const unPartialProfit = strategyCommitSubject
|
|
30639
30888
|
.filter(({ action }) => action === "partial-profit")
|
|
30640
30889
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -30676,7 +30925,7 @@ class StrategyMarkdownService {
|
|
|
30676
30925
|
exchangeName: event.exchangeName,
|
|
30677
30926
|
frameName: event.frameName,
|
|
30678
30927
|
strategyName: event.strategyName,
|
|
30679
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
|
|
30928
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
|
|
30680
30929
|
const unAverageBuy = strategyCommitSubject
|
|
30681
30930
|
.filter(({ action }) => action === "average-buy")
|
|
30682
30931
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -30712,7 +30961,7 @@ class StrategyMarkdownService {
|
|
|
30712
30961
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
30713
30962
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
30714
30963
|
*/
|
|
30715
|
-
const CREATE_KEY_FN$
|
|
30964
|
+
const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30716
30965
|
const parts = [symbol, strategyName, exchangeName];
|
|
30717
30966
|
if (frameName)
|
|
30718
30967
|
parts.push(frameName);
|
|
@@ -30905,7 +31154,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
30905
31154
|
class SyncMarkdownService {
|
|
30906
31155
|
constructor() {
|
|
30907
31156
|
this.loggerService = inject(TYPES.loggerService);
|
|
30908
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31157
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
|
|
30909
31158
|
/**
|
|
30910
31159
|
* Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
|
|
30911
31160
|
* Protected against multiple subscriptions via `singleshot` — subsequent calls
|
|
@@ -31101,7 +31350,7 @@ class SyncMarkdownService {
|
|
|
31101
31350
|
this.clear = async (payload) => {
|
|
31102
31351
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
31103
31352
|
if (payload) {
|
|
31104
|
-
const key = CREATE_KEY_FN$
|
|
31353
|
+
const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31105
31354
|
this.getStorage.clear(key);
|
|
31106
31355
|
}
|
|
31107
31356
|
else {
|
|
@@ -31114,7 +31363,7 @@ class SyncMarkdownService {
|
|
|
31114
31363
|
/**
|
|
31115
31364
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31116
31365
|
*/
|
|
31117
|
-
const CREATE_KEY_FN$
|
|
31366
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31118
31367
|
const parts = [symbol, strategyName, exchangeName];
|
|
31119
31368
|
if (frameName)
|
|
31120
31369
|
parts.push(frameName);
|
|
@@ -31290,7 +31539,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
31290
31539
|
class HighestProfitMarkdownService {
|
|
31291
31540
|
constructor() {
|
|
31292
31541
|
this.loggerService = inject(TYPES.loggerService);
|
|
31293
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31542
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
31294
31543
|
/**
|
|
31295
31544
|
* Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
|
|
31296
31545
|
* events. Protected against multiple subscriptions via `singleshot` — subsequent
|
|
@@ -31456,7 +31705,7 @@ class HighestProfitMarkdownService {
|
|
|
31456
31705
|
this.clear = async (payload) => {
|
|
31457
31706
|
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
31458
31707
|
if (payload) {
|
|
31459
|
-
const key = CREATE_KEY_FN$
|
|
31708
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31460
31709
|
this.getStorage.clear(key);
|
|
31461
31710
|
}
|
|
31462
31711
|
else {
|
|
@@ -31478,7 +31727,7 @@ const LISTEN_TIMEOUT$1 = 120000;
|
|
|
31478
31727
|
* @param backtest - Whether running in backtest mode
|
|
31479
31728
|
* @returns Unique string key for memoization
|
|
31480
31729
|
*/
|
|
31481
|
-
const CREATE_KEY_FN$
|
|
31730
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31482
31731
|
const parts = [symbol, strategyName, exchangeName];
|
|
31483
31732
|
if (frameName)
|
|
31484
31733
|
parts.push(frameName);
|
|
@@ -31521,7 +31770,7 @@ class PriceMetaService {
|
|
|
31521
31770
|
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
31522
31771
|
* Instances are cached until clear() is called.
|
|
31523
31772
|
*/
|
|
31524
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31773
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31525
31774
|
/**
|
|
31526
31775
|
* Returns the current market price for the given symbol and context.
|
|
31527
31776
|
*
|
|
@@ -31550,10 +31799,10 @@ class PriceMetaService {
|
|
|
31550
31799
|
if (source.data) {
|
|
31551
31800
|
return source.data;
|
|
31552
31801
|
}
|
|
31553
|
-
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$
|
|
31802
|
+
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31554
31803
|
const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
31555
31804
|
if (typeof currentPrice === "symbol") {
|
|
31556
|
-
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$
|
|
31805
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31557
31806
|
}
|
|
31558
31807
|
return currentPrice;
|
|
31559
31808
|
};
|
|
@@ -31595,7 +31844,7 @@ class PriceMetaService {
|
|
|
31595
31844
|
this.getSource.clear();
|
|
31596
31845
|
return;
|
|
31597
31846
|
}
|
|
31598
|
-
const key = CREATE_KEY_FN$
|
|
31847
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31599
31848
|
this.getSource.clear(key);
|
|
31600
31849
|
};
|
|
31601
31850
|
}
|
|
@@ -31613,7 +31862,7 @@ const LISTEN_TIMEOUT = 120000;
|
|
|
31613
31862
|
* @param backtest - Whether running in backtest mode
|
|
31614
31863
|
* @returns Unique string key for memoization
|
|
31615
31864
|
*/
|
|
31616
|
-
const CREATE_KEY_FN$
|
|
31865
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31617
31866
|
const parts = [symbol, strategyName, exchangeName];
|
|
31618
31867
|
if (frameName)
|
|
31619
31868
|
parts.push(frameName);
|
|
@@ -31656,7 +31905,7 @@ class TimeMetaService {
|
|
|
31656
31905
|
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
31657
31906
|
* Instances are cached until clear() is called.
|
|
31658
31907
|
*/
|
|
31659
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31908
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31660
31909
|
/**
|
|
31661
31910
|
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
31662
31911
|
*
|
|
@@ -31684,10 +31933,10 @@ class TimeMetaService {
|
|
|
31684
31933
|
if (source.data) {
|
|
31685
31934
|
return source.data;
|
|
31686
31935
|
}
|
|
31687
|
-
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$
|
|
31936
|
+
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31688
31937
|
const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
31689
31938
|
if (typeof timestamp === "symbol") {
|
|
31690
|
-
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$
|
|
31939
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31691
31940
|
}
|
|
31692
31941
|
return timestamp;
|
|
31693
31942
|
};
|
|
@@ -31729,7 +31978,7 @@ class TimeMetaService {
|
|
|
31729
31978
|
this.getSource.clear();
|
|
31730
31979
|
return;
|
|
31731
31980
|
}
|
|
31732
|
-
const key = CREATE_KEY_FN$
|
|
31981
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31733
31982
|
this.getSource.clear(key);
|
|
31734
31983
|
};
|
|
31735
31984
|
}
|
|
@@ -31825,7 +32074,7 @@ class MaxDrawdownReportService {
|
|
|
31825
32074
|
/**
|
|
31826
32075
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31827
32076
|
*/
|
|
31828
|
-
const CREATE_KEY_FN$
|
|
32077
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31829
32078
|
const parts = [symbol, strategyName, exchangeName];
|
|
31830
32079
|
if (frameName)
|
|
31831
32080
|
parts.push(frameName);
|
|
@@ -31949,7 +32198,7 @@ class ReportStorage {
|
|
|
31949
32198
|
class MaxDrawdownMarkdownService {
|
|
31950
32199
|
constructor() {
|
|
31951
32200
|
this.loggerService = inject(TYPES.loggerService);
|
|
31952
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
32201
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
31953
32202
|
/**
|
|
31954
32203
|
* Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
|
|
31955
32204
|
* events. Protected against multiple subscriptions via `singleshot`.
|
|
@@ -32028,7 +32277,7 @@ class MaxDrawdownMarkdownService {
|
|
|
32028
32277
|
this.clear = async (payload) => {
|
|
32029
32278
|
this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
|
|
32030
32279
|
if (payload) {
|
|
32031
|
-
const key = CREATE_KEY_FN$
|
|
32280
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
32032
32281
|
this.getStorage.clear(key);
|
|
32033
32282
|
}
|
|
32034
32283
|
else {
|
|
@@ -35161,6 +35410,8 @@ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strateg
|
|
|
35161
35410
|
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35162
35411
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35163
35412
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
35413
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlPercentage";
|
|
35414
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlCost";
|
|
35164
35415
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
35165
35416
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
35166
35417
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -35176,7 +35427,7 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35176
35427
|
*
|
|
35177
35428
|
* @param symbol - Trading pair symbol
|
|
35178
35429
|
* @param strategyName - Strategy name
|
|
35179
|
-
* @param
|
|
35430
|
+
* @param payload - Optional commit payload with id and note
|
|
35180
35431
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
35181
35432
|
*
|
|
35182
35433
|
* @example
|
|
@@ -35184,13 +35435,13 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35184
35435
|
* import { commitCancelScheduled } from "backtest-kit";
|
|
35185
35436
|
*
|
|
35186
35437
|
* // Cancel scheduled signal with custom ID
|
|
35187
|
-
* await commitCancelScheduled("BTCUSDT", "manual-cancel-001");
|
|
35438
|
+
* await commitCancelScheduled("BTCUSDT", { id: "manual-cancel-001" });
|
|
35188
35439
|
* ```
|
|
35189
35440
|
*/
|
|
35190
|
-
async function commitCancelScheduled(symbol,
|
|
35441
|
+
async function commitCancelScheduled(symbol, payload = {}) {
|
|
35191
35442
|
backtest.loggerService.info(CANCEL_SCHEDULED_METHOD_NAME, {
|
|
35192
35443
|
symbol,
|
|
35193
|
-
|
|
35444
|
+
payload,
|
|
35194
35445
|
});
|
|
35195
35446
|
if (!ExecutionContextService.hasContext()) {
|
|
35196
35447
|
throw new Error("commitCancelScheduled requires an execution context");
|
|
@@ -35200,7 +35451,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35200
35451
|
}
|
|
35201
35452
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35202
35453
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35203
|
-
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35454
|
+
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35204
35455
|
}
|
|
35205
35456
|
/**
|
|
35206
35457
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -35212,7 +35463,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35212
35463
|
* Automatically detects backtest/live mode from execution context.
|
|
35213
35464
|
*
|
|
35214
35465
|
* @param symbol - Trading pair symbol
|
|
35215
|
-
* @param
|
|
35466
|
+
* @param payload - Optional commit payload with id and note
|
|
35216
35467
|
* @returns Promise that resolves when pending signal is closed
|
|
35217
35468
|
*
|
|
35218
35469
|
* @example
|
|
@@ -35220,13 +35471,13 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35220
35471
|
* import { commitClosePending } from "backtest-kit";
|
|
35221
35472
|
*
|
|
35222
35473
|
* // Close pending signal with custom ID
|
|
35223
|
-
* await commitClosePending("BTCUSDT", "manual-close-001");
|
|
35474
|
+
* await commitClosePending("BTCUSDT", { id: "manual-close-001" });
|
|
35224
35475
|
* ```
|
|
35225
35476
|
*/
|
|
35226
|
-
async function commitClosePending(symbol,
|
|
35477
|
+
async function commitClosePending(symbol, payload = {}) {
|
|
35227
35478
|
backtest.loggerService.info(CLOSE_PENDING_METHOD_NAME, {
|
|
35228
35479
|
symbol,
|
|
35229
|
-
|
|
35480
|
+
payload,
|
|
35230
35481
|
});
|
|
35231
35482
|
if (!ExecutionContextService.hasContext()) {
|
|
35232
35483
|
throw new Error("commitClosePending requires an execution context");
|
|
@@ -35236,7 +35487,7 @@ async function commitClosePending(symbol, closeId) {
|
|
|
35236
35487
|
}
|
|
35237
35488
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35238
35489
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35239
|
-
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35490
|
+
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35240
35491
|
}
|
|
35241
35492
|
/**
|
|
35242
35493
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -35699,7 +35950,7 @@ async function commitBreakeven(symbol) {
|
|
|
35699
35950
|
* Automatically detects backtest/live mode from execution context.
|
|
35700
35951
|
*
|
|
35701
35952
|
* @param symbol - Trading pair symbol
|
|
35702
|
-
* @param
|
|
35953
|
+
* @param payload - Optional commit payload with id and note
|
|
35703
35954
|
* @returns Promise that resolves when activation flag is set
|
|
35704
35955
|
*
|
|
35705
35956
|
* @example
|
|
@@ -35707,13 +35958,13 @@ async function commitBreakeven(symbol) {
|
|
|
35707
35958
|
* import { commitActivateScheduled } from "backtest-kit";
|
|
35708
35959
|
*
|
|
35709
35960
|
* // Activate scheduled signal early with custom ID
|
|
35710
|
-
* await commitActivateScheduled("BTCUSDT", "manual-activate-001");
|
|
35961
|
+
* await commitActivateScheduled("BTCUSDT", { id: "manual-activate-001" });
|
|
35711
35962
|
* ```
|
|
35712
35963
|
*/
|
|
35713
|
-
async function commitActivateScheduled(symbol,
|
|
35964
|
+
async function commitActivateScheduled(symbol, payload = {}) {
|
|
35714
35965
|
backtest.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
|
|
35715
35966
|
symbol,
|
|
35716
|
-
|
|
35967
|
+
payload,
|
|
35717
35968
|
});
|
|
35718
35969
|
if (!ExecutionContextService.hasContext()) {
|
|
35719
35970
|
throw new Error("commitActivateScheduled requires an execution context");
|
|
@@ -35723,7 +35974,7 @@ async function commitActivateScheduled(symbol, activateId) {
|
|
|
35723
35974
|
}
|
|
35724
35975
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35725
35976
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35726
|
-
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35977
|
+
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35727
35978
|
}
|
|
35728
35979
|
/**
|
|
35729
35980
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -36911,6 +37162,64 @@ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
|
36911
37162
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36912
37163
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36913
37164
|
}
|
|
37165
|
+
/**
|
|
37166
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
37167
|
+
*
|
|
37168
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
37169
|
+
* Returns null if no pending signal exists.
|
|
37170
|
+
*
|
|
37171
|
+
* @param symbol - Trading pair symbol
|
|
37172
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
37173
|
+
*
|
|
37174
|
+
* @example
|
|
37175
|
+
* ```typescript
|
|
37176
|
+
* import { getMaxDrawdownDistancePnlPercentage } from "backtest-kit";
|
|
37177
|
+
*
|
|
37178
|
+
* const dist = await getMaxDrawdownDistancePnlPercentage("BTCUSDT");
|
|
37179
|
+
* // e.g. 3.5 (peak was +3.5% above trough)
|
|
37180
|
+
* ```
|
|
37181
|
+
*/
|
|
37182
|
+
async function getMaxDrawdownDistancePnlPercentage(symbol) {
|
|
37183
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
37184
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37185
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires an execution context");
|
|
37186
|
+
}
|
|
37187
|
+
if (!MethodContextService.hasContext()) {
|
|
37188
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires a method context");
|
|
37189
|
+
}
|
|
37190
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37191
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37192
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37193
|
+
}
|
|
37194
|
+
/**
|
|
37195
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
37196
|
+
*
|
|
37197
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
37198
|
+
* Returns null if no pending signal exists.
|
|
37199
|
+
*
|
|
37200
|
+
* @param symbol - Trading pair symbol
|
|
37201
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
37202
|
+
*
|
|
37203
|
+
* @example
|
|
37204
|
+
* ```typescript
|
|
37205
|
+
* import { getMaxDrawdownDistancePnlCost } from "backtest-kit";
|
|
37206
|
+
*
|
|
37207
|
+
* const dist = await getMaxDrawdownDistancePnlCost("BTCUSDT");
|
|
37208
|
+
* // e.g. 7.2 (peak was $7.2 above trough)
|
|
37209
|
+
* ```
|
|
37210
|
+
*/
|
|
37211
|
+
async function getMaxDrawdownDistancePnlCost(symbol) {
|
|
37212
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
37213
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37214
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires an execution context");
|
|
37215
|
+
}
|
|
37216
|
+
if (!MethodContextService.hasContext()) {
|
|
37217
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires a method context");
|
|
37218
|
+
}
|
|
37219
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37220
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37221
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37222
|
+
}
|
|
36914
37223
|
/**
|
|
36915
37224
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36916
37225
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38584,6 +38893,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE =
|
|
|
38584
38893
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38585
38894
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38586
38895
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38896
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getMaxDrawdownDistancePnlPercentage";
|
|
38897
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "BacktestUtils.getMaxDrawdownDistancePnlCost";
|
|
38587
38898
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38588
38899
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38589
38900
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39965,6 +40276,62 @@ class BacktestUtils {
|
|
|
39965
40276
|
}
|
|
39966
40277
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39967
40278
|
};
|
|
40279
|
+
/**
|
|
40280
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
40281
|
+
*
|
|
40282
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
40283
|
+
* Returns null if no pending signal exists.
|
|
40284
|
+
*
|
|
40285
|
+
* @param symbol - Trading pair symbol
|
|
40286
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40287
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
40288
|
+
*/
|
|
40289
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
40290
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
40291
|
+
symbol,
|
|
40292
|
+
context,
|
|
40293
|
+
});
|
|
40294
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40295
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40296
|
+
{
|
|
40297
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40298
|
+
riskName &&
|
|
40299
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40300
|
+
riskList &&
|
|
40301
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40302
|
+
actions &&
|
|
40303
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40304
|
+
}
|
|
40305
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(true, symbol, context);
|
|
40306
|
+
};
|
|
40307
|
+
/**
|
|
40308
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
40309
|
+
*
|
|
40310
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
40311
|
+
* Returns null if no pending signal exists.
|
|
40312
|
+
*
|
|
40313
|
+
* @param symbol - Trading pair symbol
|
|
40314
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40315
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
40316
|
+
*/
|
|
40317
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
40318
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
40319
|
+
symbol,
|
|
40320
|
+
context,
|
|
40321
|
+
});
|
|
40322
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40323
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40324
|
+
{
|
|
40325
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40326
|
+
riskName &&
|
|
40327
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40328
|
+
riskList &&
|
|
40329
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40330
|
+
actions &&
|
|
40331
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40332
|
+
}
|
|
40333
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(true, symbol, context);
|
|
40334
|
+
};
|
|
39968
40335
|
/**
|
|
39969
40336
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39970
40337
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40099,7 +40466,7 @@ class BacktestUtils {
|
|
|
40099
40466
|
* @param symbol - Trading pair symbol
|
|
40100
40467
|
* @param strategyName - Strategy name
|
|
40101
40468
|
* @param context - Execution context with exchangeName and frameName
|
|
40102
|
-
* @param
|
|
40469
|
+
* @param payload - Optional commit payload with id and note
|
|
40103
40470
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
40104
40471
|
*
|
|
40105
40472
|
* @example
|
|
@@ -40109,14 +40476,14 @@ class BacktestUtils {
|
|
|
40109
40476
|
* exchangeName: "binance",
|
|
40110
40477
|
* frameName: "frame1",
|
|
40111
40478
|
* strategyName: "my-strategy"
|
|
40112
|
-
* }, "manual-cancel-001");
|
|
40479
|
+
* }, { id: "manual-cancel-001" });
|
|
40113
40480
|
* ```
|
|
40114
40481
|
*/
|
|
40115
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
40482
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
40116
40483
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
40117
40484
|
symbol,
|
|
40118
40485
|
context,
|
|
40119
|
-
|
|
40486
|
+
payload,
|
|
40120
40487
|
});
|
|
40121
40488
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
40122
40489
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -40129,7 +40496,7 @@ class BacktestUtils {
|
|
|
40129
40496
|
actions &&
|
|
40130
40497
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED));
|
|
40131
40498
|
}
|
|
40132
|
-
await backtest.strategyCoreService.cancelScheduled(true, symbol, context,
|
|
40499
|
+
await backtest.strategyCoreService.cancelScheduled(true, symbol, context, payload);
|
|
40133
40500
|
};
|
|
40134
40501
|
/**
|
|
40135
40502
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -40140,7 +40507,7 @@ class BacktestUtils {
|
|
|
40140
40507
|
*
|
|
40141
40508
|
* @param symbol - Trading pair symbol
|
|
40142
40509
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40143
|
-
* @param
|
|
40510
|
+
* @param payload - Optional commit payload with id and note
|
|
40144
40511
|
* @returns Promise that resolves when pending signal is closed
|
|
40145
40512
|
*
|
|
40146
40513
|
* @example
|
|
@@ -40150,14 +40517,14 @@ class BacktestUtils {
|
|
|
40150
40517
|
* exchangeName: "binance",
|
|
40151
40518
|
* strategyName: "my-strategy",
|
|
40152
40519
|
* frameName: "1m"
|
|
40153
|
-
* }, "manual-close-001");
|
|
40520
|
+
* }, { id: "manual-close-001" });
|
|
40154
40521
|
* ```
|
|
40155
40522
|
*/
|
|
40156
|
-
this.commitClosePending = async (symbol, context,
|
|
40523
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
40157
40524
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CLOSE_PENDING, {
|
|
40158
40525
|
symbol,
|
|
40159
40526
|
context,
|
|
40160
|
-
|
|
40527
|
+
payload,
|
|
40161
40528
|
});
|
|
40162
40529
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
40163
40530
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -40170,7 +40537,7 @@ class BacktestUtils {
|
|
|
40170
40537
|
actions &&
|
|
40171
40538
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CLOSE_PENDING));
|
|
40172
40539
|
}
|
|
40173
|
-
await backtest.strategyCoreService.closePending(true, symbol, context,
|
|
40540
|
+
await backtest.strategyCoreService.closePending(true, symbol, context, payload);
|
|
40174
40541
|
};
|
|
40175
40542
|
/**
|
|
40176
40543
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -40806,7 +41173,7 @@ class BacktestUtils {
|
|
|
40806
41173
|
*
|
|
40807
41174
|
* @param symbol - Trading pair symbol
|
|
40808
41175
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40809
|
-
* @param
|
|
41176
|
+
* @param payload - Optional commit payload with id and note
|
|
40810
41177
|
* @returns Promise that resolves when activation flag is set
|
|
40811
41178
|
*
|
|
40812
41179
|
* @example
|
|
@@ -40816,14 +41183,14 @@ class BacktestUtils {
|
|
|
40816
41183
|
* strategyName: "my-strategy",
|
|
40817
41184
|
* exchangeName: "binance",
|
|
40818
41185
|
* frameName: "1h"
|
|
40819
|
-
* }, "manual-activate-001");
|
|
41186
|
+
* }, { id: "manual-activate-001" });
|
|
40820
41187
|
* ```
|
|
40821
41188
|
*/
|
|
40822
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
41189
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
40823
41190
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
40824
41191
|
symbol,
|
|
40825
41192
|
context,
|
|
40826
|
-
|
|
41193
|
+
payload,
|
|
40827
41194
|
});
|
|
40828
41195
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
40829
41196
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -40836,7 +41203,7 @@ class BacktestUtils {
|
|
|
40836
41203
|
actions &&
|
|
40837
41204
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
40838
41205
|
}
|
|
40839
|
-
await backtest.strategyCoreService.activateScheduled(true, symbol, context,
|
|
41206
|
+
await backtest.strategyCoreService.activateScheduled(true, symbol, context, payload);
|
|
40840
41207
|
};
|
|
40841
41208
|
/**
|
|
40842
41209
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -41094,6 +41461,8 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "Li
|
|
|
41094
41461
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41095
41462
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41096
41463
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
41464
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getMaxDrawdownDistancePnlPercentage";
|
|
41465
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "LiveUtils.getMaxDrawdownDistancePnlCost";
|
|
41097
41466
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
41098
41467
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
41099
41468
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -42618,6 +42987,70 @@ class LiveUtils {
|
|
|
42618
42987
|
frameName: "",
|
|
42619
42988
|
});
|
|
42620
42989
|
};
|
|
42990
|
+
/**
|
|
42991
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
42992
|
+
*
|
|
42993
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
42994
|
+
* Returns null if no pending signal exists.
|
|
42995
|
+
*
|
|
42996
|
+
* @param symbol - Trading pair symbol
|
|
42997
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42998
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
42999
|
+
*/
|
|
43000
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
43001
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
43002
|
+
symbol,
|
|
43003
|
+
context,
|
|
43004
|
+
});
|
|
43005
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
43006
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
43007
|
+
{
|
|
43008
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
43009
|
+
riskName &&
|
|
43010
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
43011
|
+
riskList &&
|
|
43012
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
43013
|
+
actions &&
|
|
43014
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
43015
|
+
}
|
|
43016
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(false, symbol, {
|
|
43017
|
+
strategyName: context.strategyName,
|
|
43018
|
+
exchangeName: context.exchangeName,
|
|
43019
|
+
frameName: "",
|
|
43020
|
+
});
|
|
43021
|
+
};
|
|
43022
|
+
/**
|
|
43023
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
43024
|
+
*
|
|
43025
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
43026
|
+
* Returns null if no pending signal exists.
|
|
43027
|
+
*
|
|
43028
|
+
* @param symbol - Trading pair symbol
|
|
43029
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
43030
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
43031
|
+
*/
|
|
43032
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
43033
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
43034
|
+
symbol,
|
|
43035
|
+
context,
|
|
43036
|
+
});
|
|
43037
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
43038
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
43039
|
+
{
|
|
43040
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
43041
|
+
riskName &&
|
|
43042
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
43043
|
+
riskList &&
|
|
43044
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
43045
|
+
actions &&
|
|
43046
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
43047
|
+
}
|
|
43048
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(false, symbol, {
|
|
43049
|
+
strategyName: context.strategyName,
|
|
43050
|
+
exchangeName: context.exchangeName,
|
|
43051
|
+
frameName: "",
|
|
43052
|
+
});
|
|
43053
|
+
};
|
|
42621
43054
|
/**
|
|
42622
43055
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
42623
43056
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -42759,7 +43192,7 @@ class LiveUtils {
|
|
|
42759
43192
|
* @param symbol - Trading pair symbol
|
|
42760
43193
|
* @param strategyName - Strategy name
|
|
42761
43194
|
* @param context - Execution context with exchangeName and frameName
|
|
42762
|
-
* @param
|
|
43195
|
+
* @param payload - Optional commit payload with id and note
|
|
42763
43196
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
42764
43197
|
*
|
|
42765
43198
|
* @example
|
|
@@ -42769,14 +43202,14 @@ class LiveUtils {
|
|
|
42769
43202
|
* exchangeName: "binance",
|
|
42770
43203
|
* frameName: "",
|
|
42771
43204
|
* strategyName: "my-strategy"
|
|
42772
|
-
* }, "manual-cancel-001");
|
|
43205
|
+
* }, { id: "manual-cancel-001" });
|
|
42773
43206
|
* ```
|
|
42774
43207
|
*/
|
|
42775
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
43208
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
42776
43209
|
backtest.loggerService.info(LIVE_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
42777
43210
|
symbol,
|
|
42778
43211
|
context,
|
|
42779
|
-
|
|
43212
|
+
payload,
|
|
42780
43213
|
});
|
|
42781
43214
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
42782
43215
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -42793,7 +43226,7 @@ class LiveUtils {
|
|
|
42793
43226
|
strategyName: context.strategyName,
|
|
42794
43227
|
exchangeName: context.exchangeName,
|
|
42795
43228
|
frameName: "",
|
|
42796
|
-
},
|
|
43229
|
+
}, payload);
|
|
42797
43230
|
};
|
|
42798
43231
|
/**
|
|
42799
43232
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -42804,7 +43237,7 @@ class LiveUtils {
|
|
|
42804
43237
|
*
|
|
42805
43238
|
* @param symbol - Trading pair symbol
|
|
42806
43239
|
* @param context - Execution context with strategyName and exchangeName
|
|
42807
|
-
* @param
|
|
43240
|
+
* @param payload - Optional commit payload with id and note
|
|
42808
43241
|
* @returns Promise that resolves when pending signal is closed
|
|
42809
43242
|
*
|
|
42810
43243
|
* @example
|
|
@@ -42813,14 +43246,14 @@ class LiveUtils {
|
|
|
42813
43246
|
* await Live.commitClose("BTCUSDT", {
|
|
42814
43247
|
* exchangeName: "binance",
|
|
42815
43248
|
* strategyName: "my-strategy"
|
|
42816
|
-
* }, "manual-close-001");
|
|
43249
|
+
* }, { id: "manual-close-001" });
|
|
42817
43250
|
* ```
|
|
42818
43251
|
*/
|
|
42819
|
-
this.commitClosePending = async (symbol, context,
|
|
43252
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
42820
43253
|
backtest.loggerService.info(LIVE_METHOD_NAME_CLOSE_PENDING, {
|
|
42821
43254
|
symbol,
|
|
42822
43255
|
context,
|
|
42823
|
-
|
|
43256
|
+
payload,
|
|
42824
43257
|
});
|
|
42825
43258
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
42826
43259
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -42837,7 +43270,7 @@ class LiveUtils {
|
|
|
42837
43270
|
strategyName: context.strategyName,
|
|
42838
43271
|
exchangeName: context.exchangeName,
|
|
42839
43272
|
frameName: "",
|
|
42840
|
-
},
|
|
43273
|
+
}, payload);
|
|
42841
43274
|
};
|
|
42842
43275
|
/**
|
|
42843
43276
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -43631,7 +44064,7 @@ class LiveUtils {
|
|
|
43631
44064
|
*
|
|
43632
44065
|
* @param symbol - Trading pair symbol
|
|
43633
44066
|
* @param context - Execution context with strategyName and exchangeName
|
|
43634
|
-
* @param
|
|
44067
|
+
* @param payload - Optional commit payload with id and note
|
|
43635
44068
|
* @returns Promise that resolves when activation flag is set
|
|
43636
44069
|
*
|
|
43637
44070
|
* @example
|
|
@@ -43640,14 +44073,14 @@ class LiveUtils {
|
|
|
43640
44073
|
* await Live.commitActivateScheduled("BTCUSDT", {
|
|
43641
44074
|
* strategyName: "my-strategy",
|
|
43642
44075
|
* exchangeName: "binance"
|
|
43643
|
-
* }, "manual-activate-001");
|
|
44076
|
+
* }, { id: "manual-activate-001" });
|
|
43644
44077
|
* ```
|
|
43645
44078
|
*/
|
|
43646
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
44079
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
43647
44080
|
backtest.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
43648
44081
|
symbol,
|
|
43649
44082
|
context,
|
|
43650
|
-
|
|
44083
|
+
payload,
|
|
43651
44084
|
});
|
|
43652
44085
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
43653
44086
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -43664,7 +44097,7 @@ class LiveUtils {
|
|
|
43664
44097
|
strategyName: context.strategyName,
|
|
43665
44098
|
exchangeName: context.exchangeName,
|
|
43666
44099
|
frameName: "",
|
|
43667
|
-
},
|
|
44100
|
+
}, payload);
|
|
43668
44101
|
};
|
|
43669
44102
|
/**
|
|
43670
44103
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -44910,6 +45343,503 @@ async function listRiskSchema() {
|
|
|
44910
45343
|
return await backtest.riskValidationService.list();
|
|
44911
45344
|
}
|
|
44912
45345
|
|
|
45346
|
+
const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
|
|
45347
|
+
const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
|
|
45348
|
+
const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
|
|
45349
|
+
const RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistLiveUtils.getLatestSignal";
|
|
45350
|
+
const RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryBacktestUtils.handleActivePing";
|
|
45351
|
+
const RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryBacktestUtils.getLatestSignal";
|
|
45352
|
+
const RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryLiveUtils.handleActivePing";
|
|
45353
|
+
const RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryLiveUtils.getLatestSignal";
|
|
45354
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentBacktestAdapter.handleActivePing";
|
|
45355
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentBacktestAdapter.getLatestSignal";
|
|
45356
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentBacktestAdapter.useRecentAdapter";
|
|
45357
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentBacktestAdapter.usePersist";
|
|
45358
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentBacktestAdapter.useMemory";
|
|
45359
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "RecentBacktestAdapter.clear";
|
|
45360
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentLiveAdapter.handleActivePing";
|
|
45361
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentLiveAdapter.getLatestSignal";
|
|
45362
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentLiveAdapter.useRecentAdapter";
|
|
45363
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentLiveAdapter.usePersist";
|
|
45364
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentLiveAdapter.useMemory";
|
|
45365
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
|
|
45366
|
+
const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
|
|
45367
|
+
const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
|
|
45368
|
+
const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
|
|
45369
|
+
/**
|
|
45370
|
+
* Builds a composite storage key from context parts.
|
|
45371
|
+
* Includes backtest flag as the last segment to prevent live/backtest collisions.
|
|
45372
|
+
* @param symbol - Trading pair symbol
|
|
45373
|
+
* @param strategyName - Strategy identifier
|
|
45374
|
+
* @param exchangeName - Exchange identifier
|
|
45375
|
+
* @param frameName - Frame identifier
|
|
45376
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45377
|
+
* @returns Composite key string
|
|
45378
|
+
*/
|
|
45379
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
45380
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
45381
|
+
if (frameName)
|
|
45382
|
+
parts.push(frameName);
|
|
45383
|
+
parts.push(backtest ? "backtest" : "live");
|
|
45384
|
+
return parts.join(":");
|
|
45385
|
+
};
|
|
45386
|
+
/**
|
|
45387
|
+
* Persistent storage adapter for backtest recent signals.
|
|
45388
|
+
*
|
|
45389
|
+
* Features:
|
|
45390
|
+
* - Persists the latest active signal per context to disk using PersistRecentAdapter
|
|
45391
|
+
* - Handles active ping events only
|
|
45392
|
+
*
|
|
45393
|
+
* Use this adapter for backtest recent signal persistence across sessions.
|
|
45394
|
+
*/
|
|
45395
|
+
class RecentPersistBacktestUtils {
|
|
45396
|
+
constructor() {
|
|
45397
|
+
/**
|
|
45398
|
+
* Handles active ping event.
|
|
45399
|
+
* Persists the latest signal to disk via PersistRecentAdapter.
|
|
45400
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45401
|
+
*/
|
|
45402
|
+
this.handleActivePing = async (event) => {
|
|
45403
|
+
backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45404
|
+
signalId: event.data.id,
|
|
45405
|
+
});
|
|
45406
|
+
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45407
|
+
};
|
|
45408
|
+
/**
|
|
45409
|
+
* Retrieves the latest persisted signal for the given context.
|
|
45410
|
+
* @param symbol - Trading pair symbol
|
|
45411
|
+
* @param strategyName - Strategy identifier
|
|
45412
|
+
* @param exchangeName - Exchange identifier
|
|
45413
|
+
* @param frameName - Frame identifier
|
|
45414
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45415
|
+
* @returns The latest signal or null if not found
|
|
45416
|
+
*/
|
|
45417
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45418
|
+
backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45419
|
+
symbol,
|
|
45420
|
+
strategyName,
|
|
45421
|
+
exchangeName,
|
|
45422
|
+
frameName,
|
|
45423
|
+
backtest: backtest$1,
|
|
45424
|
+
});
|
|
45425
|
+
return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45426
|
+
};
|
|
45427
|
+
}
|
|
45428
|
+
}
|
|
45429
|
+
/**
|
|
45430
|
+
* In-memory storage adapter for backtest recent signals.
|
|
45431
|
+
*
|
|
45432
|
+
* Features:
|
|
45433
|
+
* - Stores the latest active signal per context key in memory only
|
|
45434
|
+
* - Fast read/write operations
|
|
45435
|
+
* - Data is lost when application restarts
|
|
45436
|
+
*
|
|
45437
|
+
* Use this adapter for testing or when persistence is not required.
|
|
45438
|
+
*/
|
|
45439
|
+
class RecentMemoryBacktestUtils {
|
|
45440
|
+
constructor() {
|
|
45441
|
+
/** Map of composite context keys to the latest signal */
|
|
45442
|
+
this._signals = new Map();
|
|
45443
|
+
/**
|
|
45444
|
+
* Handles active ping event.
|
|
45445
|
+
* Stores the latest signal in memory under the composite context key.
|
|
45446
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45447
|
+
*/
|
|
45448
|
+
this.handleActivePing = async (event) => {
|
|
45449
|
+
backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45450
|
+
signalId: event.data.id,
|
|
45451
|
+
});
|
|
45452
|
+
const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45453
|
+
this._signals.set(key, event.data);
|
|
45454
|
+
};
|
|
45455
|
+
/**
|
|
45456
|
+
* Retrieves the latest in-memory signal for the given context.
|
|
45457
|
+
* @param symbol - Trading pair symbol
|
|
45458
|
+
* @param strategyName - Strategy identifier
|
|
45459
|
+
* @param exchangeName - Exchange identifier
|
|
45460
|
+
* @param frameName - Frame identifier
|
|
45461
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45462
|
+
* @returns The latest signal or null if not found
|
|
45463
|
+
*/
|
|
45464
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45465
|
+
const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45466
|
+
backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
45467
|
+
return this._signals.get(key) ?? null;
|
|
45468
|
+
};
|
|
45469
|
+
}
|
|
45470
|
+
}
|
|
45471
|
+
/**
|
|
45472
|
+
* Persistent storage adapter for live recent signals.
|
|
45473
|
+
*
|
|
45474
|
+
* Features:
|
|
45475
|
+
* - Persists the latest active signal per context to disk using PersistRecentAdapter
|
|
45476
|
+
* - Handles active ping events only
|
|
45477
|
+
*
|
|
45478
|
+
* Use this adapter (default) for live recent signal persistence across sessions.
|
|
45479
|
+
*/
|
|
45480
|
+
class RecentPersistLiveUtils {
|
|
45481
|
+
constructor() {
|
|
45482
|
+
/**
|
|
45483
|
+
* Handles active ping event.
|
|
45484
|
+
* Persists the latest signal to disk via PersistRecentAdapter.
|
|
45485
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45486
|
+
*/
|
|
45487
|
+
this.handleActivePing = async (event) => {
|
|
45488
|
+
backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45489
|
+
signalId: event.data.id,
|
|
45490
|
+
});
|
|
45491
|
+
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45492
|
+
};
|
|
45493
|
+
/**
|
|
45494
|
+
* Retrieves the latest persisted signal for the given context.
|
|
45495
|
+
* @param symbol - Trading pair symbol
|
|
45496
|
+
* @param strategyName - Strategy identifier
|
|
45497
|
+
* @param exchangeName - Exchange identifier
|
|
45498
|
+
* @param frameName - Frame identifier
|
|
45499
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45500
|
+
* @returns The latest signal or null if not found
|
|
45501
|
+
*/
|
|
45502
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45503
|
+
backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45504
|
+
symbol,
|
|
45505
|
+
strategyName,
|
|
45506
|
+
exchangeName,
|
|
45507
|
+
frameName,
|
|
45508
|
+
backtest: backtest$1,
|
|
45509
|
+
});
|
|
45510
|
+
return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45511
|
+
};
|
|
45512
|
+
}
|
|
45513
|
+
}
|
|
45514
|
+
/**
|
|
45515
|
+
* In-memory storage adapter for live recent signals.
|
|
45516
|
+
*
|
|
45517
|
+
* Features:
|
|
45518
|
+
* - Stores the latest active signal per context key in memory only
|
|
45519
|
+
* - Fast read/write operations
|
|
45520
|
+
* - Data is lost when application restarts
|
|
45521
|
+
*
|
|
45522
|
+
* Use this adapter for testing or when persistence is not required.
|
|
45523
|
+
*/
|
|
45524
|
+
class RecentMemoryLiveUtils {
|
|
45525
|
+
constructor() {
|
|
45526
|
+
/** Map of composite context keys to the latest signal */
|
|
45527
|
+
this._signals = new Map();
|
|
45528
|
+
/**
|
|
45529
|
+
* Handles active ping event.
|
|
45530
|
+
* Stores the latest signal in memory under the composite context key.
|
|
45531
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45532
|
+
*/
|
|
45533
|
+
this.handleActivePing = async (event) => {
|
|
45534
|
+
backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45535
|
+
signalId: event.data.id,
|
|
45536
|
+
});
|
|
45537
|
+
const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45538
|
+
this._signals.set(key, event.data);
|
|
45539
|
+
};
|
|
45540
|
+
/**
|
|
45541
|
+
* Retrieves the latest in-memory signal for the given context.
|
|
45542
|
+
* @param symbol - Trading pair symbol
|
|
45543
|
+
* @param strategyName - Strategy identifier
|
|
45544
|
+
* @param exchangeName - Exchange identifier
|
|
45545
|
+
* @param frameName - Frame identifier
|
|
45546
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45547
|
+
* @returns The latest signal or null if not found
|
|
45548
|
+
*/
|
|
45549
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45550
|
+
const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45551
|
+
backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
45552
|
+
return this._signals.get(key) ?? null;
|
|
45553
|
+
};
|
|
45554
|
+
}
|
|
45555
|
+
}
|
|
45556
|
+
/**
|
|
45557
|
+
* Backtest recent signal adapter with pluggable storage backend.
|
|
45558
|
+
*
|
|
45559
|
+
* Features:
|
|
45560
|
+
* - Adapter pattern for swappable storage implementations
|
|
45561
|
+
* - Default adapter: RecentMemoryBacktestUtils (in-memory storage)
|
|
45562
|
+
* - Alternative adapter: RecentPersistBacktestUtils
|
|
45563
|
+
* - Convenience methods: usePersist(), useMemory()
|
|
45564
|
+
*/
|
|
45565
|
+
class RecentBacktestAdapter {
|
|
45566
|
+
constructor() {
|
|
45567
|
+
/** Internal storage utils instance */
|
|
45568
|
+
this._recentBacktestUtils = new RecentMemoryBacktestUtils();
|
|
45569
|
+
/**
|
|
45570
|
+
* Handles active ping event.
|
|
45571
|
+
* Proxies call to the underlying storage adapter.
|
|
45572
|
+
* @param event - Active ping contract with signal data
|
|
45573
|
+
*/
|
|
45574
|
+
this.handleActivePing = async (event) => {
|
|
45575
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45576
|
+
signalId: event.data.id,
|
|
45577
|
+
});
|
|
45578
|
+
return await this._recentBacktestUtils.handleActivePing(event);
|
|
45579
|
+
};
|
|
45580
|
+
/**
|
|
45581
|
+
* Retrieves the latest signal for the given context.
|
|
45582
|
+
* Proxies call to the underlying storage adapter.
|
|
45583
|
+
* @param symbol - Trading pair symbol
|
|
45584
|
+
* @param strategyName - Strategy identifier
|
|
45585
|
+
* @param exchangeName - Exchange identifier
|
|
45586
|
+
* @param frameName - Frame identifier
|
|
45587
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45588
|
+
* @returns The latest signal or null if not found
|
|
45589
|
+
*/
|
|
45590
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45591
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45592
|
+
symbol,
|
|
45593
|
+
strategyName,
|
|
45594
|
+
exchangeName,
|
|
45595
|
+
frameName,
|
|
45596
|
+
backtest: backtest$1,
|
|
45597
|
+
});
|
|
45598
|
+
return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45599
|
+
};
|
|
45600
|
+
/**
|
|
45601
|
+
* Sets the storage adapter constructor.
|
|
45602
|
+
* All future storage operations will use this adapter.
|
|
45603
|
+
* @param Ctor - Constructor for recent adapter
|
|
45604
|
+
*/
|
|
45605
|
+
this.useRecentAdapter = (Ctor) => {
|
|
45606
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
|
|
45607
|
+
this._recentBacktestUtils = Reflect.construct(Ctor, []);
|
|
45608
|
+
};
|
|
45609
|
+
/**
|
|
45610
|
+
* Switches to persistent storage adapter.
|
|
45611
|
+
* Signals will be persisted to disk.
|
|
45612
|
+
*/
|
|
45613
|
+
this.usePersist = () => {
|
|
45614
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
|
|
45615
|
+
this._recentBacktestUtils = new RecentPersistBacktestUtils();
|
|
45616
|
+
};
|
|
45617
|
+
/**
|
|
45618
|
+
* Switches to in-memory storage adapter (default).
|
|
45619
|
+
* Signals will be stored in memory only.
|
|
45620
|
+
*/
|
|
45621
|
+
this.useMemory = () => {
|
|
45622
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY);
|
|
45623
|
+
this._recentBacktestUtils = new RecentMemoryBacktestUtils();
|
|
45624
|
+
};
|
|
45625
|
+
/**
|
|
45626
|
+
* Clears the cached utils instance by resetting to the default in-memory adapter.
|
|
45627
|
+
*/
|
|
45628
|
+
this.clear = () => {
|
|
45629
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
|
|
45630
|
+
this._recentBacktestUtils = new RecentMemoryBacktestUtils();
|
|
45631
|
+
};
|
|
45632
|
+
}
|
|
45633
|
+
}
|
|
45634
|
+
/**
|
|
45635
|
+
* Live recent signal adapter with pluggable storage backend.
|
|
45636
|
+
*
|
|
45637
|
+
* Features:
|
|
45638
|
+
* - Adapter pattern for swappable storage implementations
|
|
45639
|
+
* - Default adapter: RecentPersistLiveUtils (persistent storage)
|
|
45640
|
+
* - Alternative adapter: RecentMemoryLiveUtils
|
|
45641
|
+
* - Convenience methods: usePersist(), useMemory()
|
|
45642
|
+
*/
|
|
45643
|
+
class RecentLiveAdapter {
|
|
45644
|
+
constructor() {
|
|
45645
|
+
/** Internal storage utils instance */
|
|
45646
|
+
this._recentLiveUtils = new RecentPersistLiveUtils();
|
|
45647
|
+
/**
|
|
45648
|
+
* Handles active ping event.
|
|
45649
|
+
* Proxies call to the underlying storage adapter.
|
|
45650
|
+
* @param event - Active ping contract with signal data
|
|
45651
|
+
*/
|
|
45652
|
+
this.handleActivePing = async (event) => {
|
|
45653
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45654
|
+
signalId: event.data.id,
|
|
45655
|
+
});
|
|
45656
|
+
return await this._recentLiveUtils.handleActivePing(event);
|
|
45657
|
+
};
|
|
45658
|
+
/**
|
|
45659
|
+
* Retrieves the latest signal for the given context.
|
|
45660
|
+
* Proxies call to the underlying storage adapter.
|
|
45661
|
+
* @param symbol - Trading pair symbol
|
|
45662
|
+
* @param strategyName - Strategy identifier
|
|
45663
|
+
* @param exchangeName - Exchange identifier
|
|
45664
|
+
* @param frameName - Frame identifier
|
|
45665
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45666
|
+
* @returns The latest signal or null if not found
|
|
45667
|
+
*/
|
|
45668
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45669
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45670
|
+
symbol,
|
|
45671
|
+
strategyName,
|
|
45672
|
+
exchangeName,
|
|
45673
|
+
frameName,
|
|
45674
|
+
backtest: backtest$1,
|
|
45675
|
+
});
|
|
45676
|
+
return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45677
|
+
};
|
|
45678
|
+
/**
|
|
45679
|
+
* Sets the storage adapter constructor.
|
|
45680
|
+
* All future storage operations will use this adapter.
|
|
45681
|
+
* @param Ctor - Constructor for recent adapter
|
|
45682
|
+
*/
|
|
45683
|
+
this.useRecentAdapter = (Ctor) => {
|
|
45684
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
|
|
45685
|
+
this._recentLiveUtils = Reflect.construct(Ctor, []);
|
|
45686
|
+
};
|
|
45687
|
+
/**
|
|
45688
|
+
* Switches to persistent storage adapter (default).
|
|
45689
|
+
* Signals will be persisted to disk.
|
|
45690
|
+
*/
|
|
45691
|
+
this.usePersist = () => {
|
|
45692
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
|
|
45693
|
+
this._recentLiveUtils = new RecentPersistLiveUtils();
|
|
45694
|
+
};
|
|
45695
|
+
/**
|
|
45696
|
+
* Switches to in-memory storage adapter.
|
|
45697
|
+
* Signals will be stored in memory only.
|
|
45698
|
+
*/
|
|
45699
|
+
this.useMemory = () => {
|
|
45700
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY);
|
|
45701
|
+
this._recentLiveUtils = new RecentMemoryLiveUtils();
|
|
45702
|
+
};
|
|
45703
|
+
/**
|
|
45704
|
+
* Clears the cached utils instance by resetting to the default persistent adapter.
|
|
45705
|
+
*/
|
|
45706
|
+
this.clear = () => {
|
|
45707
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR);
|
|
45708
|
+
this._recentLiveUtils = new RecentPersistLiveUtils();
|
|
45709
|
+
};
|
|
45710
|
+
}
|
|
45711
|
+
}
|
|
45712
|
+
/**
|
|
45713
|
+
* Main recent signal adapter that manages both backtest and live recent signal storage.
|
|
45714
|
+
*
|
|
45715
|
+
* Features:
|
|
45716
|
+
* - Subscribes to activePingSubject for automatic storage updates
|
|
45717
|
+
* - Provides unified access to the latest signal for any context
|
|
45718
|
+
* - Singleshot enable pattern prevents duplicate subscriptions
|
|
45719
|
+
* - Cleanup function for proper unsubscription
|
|
45720
|
+
*/
|
|
45721
|
+
class RecentAdapter {
|
|
45722
|
+
constructor() {
|
|
45723
|
+
/**
|
|
45724
|
+
* Enables recent signal storage by subscribing to activePingSubject.
|
|
45725
|
+
* Uses singleshot to ensure one-time subscription.
|
|
45726
|
+
*
|
|
45727
|
+
* @returns Cleanup function that unsubscribes from all emitters
|
|
45728
|
+
*/
|
|
45729
|
+
this.enable = singleshot(() => {
|
|
45730
|
+
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_ENABLE);
|
|
45731
|
+
let unBacktest;
|
|
45732
|
+
let unLive;
|
|
45733
|
+
{
|
|
45734
|
+
const unBacktestPingActive = activePingSubject
|
|
45735
|
+
.filter(({ backtest }) => backtest)
|
|
45736
|
+
.connect((event) => RecentBacktest.handleActivePing(event));
|
|
45737
|
+
unBacktest = compose(() => unBacktestPingActive());
|
|
45738
|
+
}
|
|
45739
|
+
{
|
|
45740
|
+
const unLivePingActive = activePingSubject
|
|
45741
|
+
.filter(({ backtest }) => !backtest)
|
|
45742
|
+
.connect((event) => RecentLive.handleActivePing(event));
|
|
45743
|
+
unLive = compose(() => unLivePingActive());
|
|
45744
|
+
}
|
|
45745
|
+
const unEnable = () => this.enable.clear();
|
|
45746
|
+
return compose(() => unBacktest(), () => unLive(), () => unEnable());
|
|
45747
|
+
});
|
|
45748
|
+
/**
|
|
45749
|
+
* Disables recent signal storage by unsubscribing from all emitters.
|
|
45750
|
+
* Safe to call multiple times.
|
|
45751
|
+
*/
|
|
45752
|
+
this.disable = () => {
|
|
45753
|
+
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_DISABLE);
|
|
45754
|
+
if (this.enable.hasValue()) {
|
|
45755
|
+
const lastSubscription = this.enable();
|
|
45756
|
+
lastSubscription();
|
|
45757
|
+
}
|
|
45758
|
+
};
|
|
45759
|
+
/**
|
|
45760
|
+
* Retrieves the latest active signal for the given symbol and context.
|
|
45761
|
+
* Searches backtest storage first, then live storage.
|
|
45762
|
+
*
|
|
45763
|
+
* @param symbol - Trading pair symbol
|
|
45764
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
45765
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45766
|
+
* @returns The latest signal or null if not found
|
|
45767
|
+
* @throws Error if RecentAdapter is not enabled
|
|
45768
|
+
*/
|
|
45769
|
+
this.getLatestSignal = async (symbol, context) => {
|
|
45770
|
+
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45771
|
+
symbol,
|
|
45772
|
+
context,
|
|
45773
|
+
});
|
|
45774
|
+
if (!this.enable.hasValue()) {
|
|
45775
|
+
throw new Error("RecentAdapter is not enabled. Call enable() first.");
|
|
45776
|
+
}
|
|
45777
|
+
let result = null;
|
|
45778
|
+
if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
|
|
45779
|
+
return result;
|
|
45780
|
+
}
|
|
45781
|
+
if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
|
|
45782
|
+
return result;
|
|
45783
|
+
}
|
|
45784
|
+
return null;
|
|
45785
|
+
};
|
|
45786
|
+
}
|
|
45787
|
+
}
|
|
45788
|
+
/**
|
|
45789
|
+
* Global singleton instance of RecentAdapter.
|
|
45790
|
+
* Provides unified recent signal management for backtest and live trading.
|
|
45791
|
+
*/
|
|
45792
|
+
const Recent = new RecentAdapter();
|
|
45793
|
+
/**
|
|
45794
|
+
* Global singleton instance of RecentLiveAdapter.
|
|
45795
|
+
* Provides live trading recent signal storage with pluggable backends.
|
|
45796
|
+
*/
|
|
45797
|
+
const RecentLive = new RecentLiveAdapter();
|
|
45798
|
+
/**
|
|
45799
|
+
* Global singleton instance of RecentBacktestAdapter.
|
|
45800
|
+
* Provides backtest recent signal storage with pluggable backends.
|
|
45801
|
+
*/
|
|
45802
|
+
const RecentBacktest = new RecentBacktestAdapter();
|
|
45803
|
+
|
|
45804
|
+
const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
|
|
45805
|
+
/**
|
|
45806
|
+
* Returns the latest signal (pending or closed) for the current strategy context.
|
|
45807
|
+
*
|
|
45808
|
+
* Does not distinguish between active and closed signals — returns whichever
|
|
45809
|
+
* was recorded last. Useful for cooldown logic: e.g. skip opening a new position
|
|
45810
|
+
* for 4 hours after a stop-loss by checking the timestamp of the latest signal
|
|
45811
|
+
* regardless of its outcome.
|
|
45812
|
+
*
|
|
45813
|
+
* Searches backtest storage first, then live storage.
|
|
45814
|
+
* Returns null if no signal exists at all.
|
|
45815
|
+
*
|
|
45816
|
+
* Automatically detects backtest/live mode from execution context.
|
|
45817
|
+
*
|
|
45818
|
+
* @param symbol - Trading pair symbol
|
|
45819
|
+
* @returns Promise resolving to the latest signal or null
|
|
45820
|
+
*
|
|
45821
|
+
* @example
|
|
45822
|
+
* ```typescript
|
|
45823
|
+
* import { getLatestSignal } from "backtest-kit";
|
|
45824
|
+
*
|
|
45825
|
+
* const latest = await getLatestSignal("BTCUSDT");
|
|
45826
|
+
* if (latest && Date.now() - latest.closedAt < 4 * 60 * 60 * 1000) {
|
|
45827
|
+
* return; // cooldown after SL — skip new signal for 4 hours
|
|
45828
|
+
* }
|
|
45829
|
+
* ```
|
|
45830
|
+
*/
|
|
45831
|
+
async function getLatestSignal(symbol) {
|
|
45832
|
+
backtest.loggerService.info(GET_LATEST_SIGNAL_METHOD_NAME, { symbol });
|
|
45833
|
+
if (!ExecutionContextService.hasContext()) {
|
|
45834
|
+
throw new Error("getLatestSignal requires an execution context");
|
|
45835
|
+
}
|
|
45836
|
+
if (!MethodContextService.hasContext()) {
|
|
45837
|
+
throw new Error("getLatestSignal requires a method context");
|
|
45838
|
+
}
|
|
45839
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
45840
|
+
return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
|
|
45841
|
+
}
|
|
45842
|
+
|
|
44913
45843
|
const DEFAULT_BM25_K1 = 1.5;
|
|
44914
45844
|
const DEFAULT_BM25_B = 0.75;
|
|
44915
45845
|
const DEFAULT_BM25_SCORE = 0.5;
|
|
@@ -48160,6 +49090,67 @@ class LogAdapter {
|
|
|
48160
49090
|
*/
|
|
48161
49091
|
const Log = new LogAdapter();
|
|
48162
49092
|
|
|
49093
|
+
const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
|
|
49094
|
+
/** List of all global subjects whose listeners should be snapshotted for session isolation */
|
|
49095
|
+
const SUBJECT_ISOLATION_LIST = [
|
|
49096
|
+
activePingSubject,
|
|
49097
|
+
backtestScheduleOpenSubject,
|
|
49098
|
+
breakevenSubject,
|
|
49099
|
+
doneBacktestSubject,
|
|
49100
|
+
doneLiveSubject,
|
|
49101
|
+
errorEmitter,
|
|
49102
|
+
exitEmitter,
|
|
49103
|
+
highestProfitSubject,
|
|
49104
|
+
maxDrawdownSubject,
|
|
49105
|
+
partialLossSubject,
|
|
49106
|
+
partialProfitSubject,
|
|
49107
|
+
performanceEmitter,
|
|
49108
|
+
progressBacktestEmitter,
|
|
49109
|
+
riskSubject,
|
|
49110
|
+
schedulePingSubject,
|
|
49111
|
+
shutdownEmitter,
|
|
49112
|
+
signalBacktestEmitter,
|
|
49113
|
+
signalEmitter,
|
|
49114
|
+
signalLiveEmitter,
|
|
49115
|
+
strategyCommitSubject,
|
|
49116
|
+
syncSubject,
|
|
49117
|
+
validationSubject,
|
|
49118
|
+
];
|
|
49119
|
+
/**
|
|
49120
|
+
* Creates a snapshot function for a given subject by clearing its internal
|
|
49121
|
+
* events map and returning a restore function that can put the original listeners back.
|
|
49122
|
+
* @param subject The subject to snapshot
|
|
49123
|
+
* @returns A function that restores the subject's original listeners when called
|
|
49124
|
+
*/
|
|
49125
|
+
const CREATE_SUBJECT_SNAPSHOT_FN = (subject) => {
|
|
49126
|
+
const emitter = subject["_emitter"];
|
|
49127
|
+
const events = emitter["_events"];
|
|
49128
|
+
emitter["_events"] = {};
|
|
49129
|
+
return () => {
|
|
49130
|
+
emitter["_events"] = events;
|
|
49131
|
+
};
|
|
49132
|
+
};
|
|
49133
|
+
/**
|
|
49134
|
+
* Manages isolation of global event-bus state between backtest sessions.
|
|
49135
|
+
* Allows temporarily detaching all subject subscriptions so that one session
|
|
49136
|
+
* does not interfere with another, then restoring them afterwards.
|
|
49137
|
+
*/
|
|
49138
|
+
class SessionUtils {
|
|
49139
|
+
constructor() {
|
|
49140
|
+
/**
|
|
49141
|
+
* Snapshots the current listener state of every global subject by replacing
|
|
49142
|
+
* their internal `_events` map with an empty object.
|
|
49143
|
+
* @returns A restore function that, when called, puts all original listeners back.
|
|
49144
|
+
*/
|
|
49145
|
+
this.createSnapshot = () => {
|
|
49146
|
+
backtest.loggerService.log(METHOD_NAME_CREATE_SNAPSHOT);
|
|
49147
|
+
const snapshotList = SUBJECT_ISOLATION_LIST.map(CREATE_SUBJECT_SNAPSHOT_FN);
|
|
49148
|
+
return compose(...snapshotList);
|
|
49149
|
+
};
|
|
49150
|
+
}
|
|
49151
|
+
}
|
|
49152
|
+
const Session = new SessionUtils();
|
|
49153
|
+
|
|
48163
49154
|
const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
|
|
48164
49155
|
const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
|
|
48165
49156
|
const SCHEDULE_METHOD_NAME_DUMP = "ScheduleUtils.dump";
|
|
@@ -49735,6 +50726,741 @@ class MaxDrawdownUtils {
|
|
|
49735
50726
|
*/
|
|
49736
50727
|
const MaxDrawdown = new MaxDrawdownUtils();
|
|
49737
50728
|
|
|
50729
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT = "ReflectUtils.getPositionPnlPercent";
|
|
50730
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_COST = "ReflectUtils.getPositionPnlCost";
|
|
50731
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "ReflectUtils.getPositionHighestProfitPrice";
|
|
50732
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.getPositionHighestProfitTimestamp";
|
|
50733
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
|
|
50734
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
|
|
50735
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
|
|
50736
|
+
const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
|
|
50737
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
|
|
50738
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
|
|
50739
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "ReflectUtils.getPositionMaxDrawdownPrice";
|
|
50740
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "ReflectUtils.getPositionMaxDrawdownTimestamp";
|
|
50741
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionMaxDrawdownPnlPercentage";
|
|
50742
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionMaxDrawdownPnlCost";
|
|
50743
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
50744
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "ReflectUtils.getPositionHighestProfitDistancePnlCost";
|
|
50745
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
50746
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
50747
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getMaxDrawdownDistancePnlPercentage";
|
|
50748
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "ReflectUtils.getMaxDrawdownDistancePnlCost";
|
|
50749
|
+
/**
|
|
50750
|
+
* Utility class for real-time position reflection: PNL, peak profit, and drawdown queries.
|
|
50751
|
+
*
|
|
50752
|
+
* Provides unified access to strategyCoreService position state methods with logging
|
|
50753
|
+
* and full validation (strategy, exchange, frame, risk, actions).
|
|
50754
|
+
* Works for both live and backtest modes via the `backtest` parameter.
|
|
50755
|
+
* Exported as singleton instance for convenient usage.
|
|
50756
|
+
*
|
|
50757
|
+
* @example
|
|
50758
|
+
* ```typescript
|
|
50759
|
+
* import { Reflect } from "backtest-kit";
|
|
50760
|
+
*
|
|
50761
|
+
* // Get current unrealized PNL percentage
|
|
50762
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50763
|
+
* "BTCUSDT",
|
|
50764
|
+
* 45000,
|
|
50765
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50766
|
+
* );
|
|
50767
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50768
|
+
*
|
|
50769
|
+
* // Get peak profit reached
|
|
50770
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50771
|
+
* "BTCUSDT",
|
|
50772
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50773
|
+
* );
|
|
50774
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50775
|
+
* ```
|
|
50776
|
+
*/
|
|
50777
|
+
class ReflectUtils {
|
|
50778
|
+
constructor() {
|
|
50779
|
+
/**
|
|
50780
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
50781
|
+
*
|
|
50782
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50783
|
+
* Returns null if no pending signal exists.
|
|
50784
|
+
*
|
|
50785
|
+
* @param symbol - Trading pair symbol
|
|
50786
|
+
* @param currentPrice - Current market price
|
|
50787
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50788
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50789
|
+
* @returns Promise resolving to PNL percentage or null
|
|
50790
|
+
*
|
|
50791
|
+
* @example
|
|
50792
|
+
* ```typescript
|
|
50793
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50794
|
+
* "BTCUSDT",
|
|
50795
|
+
* 45000,
|
|
50796
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50797
|
+
* );
|
|
50798
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50799
|
+
* ```
|
|
50800
|
+
*/
|
|
50801
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50802
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
|
|
50803
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50804
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50805
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50806
|
+
{
|
|
50807
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50808
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50809
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50810
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50811
|
+
}
|
|
50812
|
+
return await backtest.strategyCoreService.getPositionPnlPercent(backtest$1, symbol, currentPrice, context);
|
|
50813
|
+
};
|
|
50814
|
+
/**
|
|
50815
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
50816
|
+
*
|
|
50817
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost.
|
|
50818
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50819
|
+
* Returns null if no pending signal exists.
|
|
50820
|
+
*
|
|
50821
|
+
* @param symbol - Trading pair symbol
|
|
50822
|
+
* @param currentPrice - Current market price
|
|
50823
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50824
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50825
|
+
* @returns Promise resolving to PNL in dollars or null
|
|
50826
|
+
*
|
|
50827
|
+
* @example
|
|
50828
|
+
* ```typescript
|
|
50829
|
+
* const pnlCost = await Reflect.getPositionPnlCost(
|
|
50830
|
+
* "BTCUSDT",
|
|
50831
|
+
* 45000,
|
|
50832
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50833
|
+
* );
|
|
50834
|
+
* console.log(`PNL: $${pnlCost}`);
|
|
50835
|
+
* ```
|
|
50836
|
+
*/
|
|
50837
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50838
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
|
|
50839
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50840
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50841
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50842
|
+
{
|
|
50843
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50844
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50845
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50846
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50847
|
+
}
|
|
50848
|
+
return await backtest.strategyCoreService.getPositionPnlCost(backtest$1, symbol, currentPrice, context);
|
|
50849
|
+
};
|
|
50850
|
+
/**
|
|
50851
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
50852
|
+
*
|
|
50853
|
+
* Returns null if no pending signal exists.
|
|
50854
|
+
*
|
|
50855
|
+
* @param symbol - Trading pair symbol
|
|
50856
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50857
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50858
|
+
* @returns Promise resolving to price or null
|
|
50859
|
+
*
|
|
50860
|
+
* @example
|
|
50861
|
+
* ```typescript
|
|
50862
|
+
* const peakPrice = await Reflect.getPositionHighestProfitPrice(
|
|
50863
|
+
* "BTCUSDT",
|
|
50864
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50865
|
+
* );
|
|
50866
|
+
* console.log(`Peak price: ${peakPrice}`);
|
|
50867
|
+
* ```
|
|
50868
|
+
*/
|
|
50869
|
+
this.getPositionHighestProfitPrice = async (symbol, context, backtest$1 = false) => {
|
|
50870
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, { symbol, context });
|
|
50871
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50872
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50873
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50874
|
+
{
|
|
50875
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50876
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50877
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50878
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50879
|
+
}
|
|
50880
|
+
return await backtest.strategyCoreService.getPositionHighestProfitPrice(backtest$1, symbol, context);
|
|
50881
|
+
};
|
|
50882
|
+
/**
|
|
50883
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
50884
|
+
*
|
|
50885
|
+
* Returns null if no pending signal exists.
|
|
50886
|
+
*
|
|
50887
|
+
* @param symbol - Trading pair symbol
|
|
50888
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50889
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50890
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
50891
|
+
*
|
|
50892
|
+
* @example
|
|
50893
|
+
* ```typescript
|
|
50894
|
+
* const ts = await Reflect.getPositionHighestProfitTimestamp(
|
|
50895
|
+
* "BTCUSDT",
|
|
50896
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50897
|
+
* );
|
|
50898
|
+
* console.log(`Peak at: ${new Date(ts).toISOString()}`);
|
|
50899
|
+
* ```
|
|
50900
|
+
*/
|
|
50901
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
50902
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, { symbol, context });
|
|
50903
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50904
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50905
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50906
|
+
{
|
|
50907
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50908
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50909
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50910
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50911
|
+
}
|
|
50912
|
+
return await backtest.strategyCoreService.getPositionHighestProfitTimestamp(backtest$1, symbol, context);
|
|
50913
|
+
};
|
|
50914
|
+
/**
|
|
50915
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
50916
|
+
*
|
|
50917
|
+
* Returns null if no pending signal exists.
|
|
50918
|
+
*
|
|
50919
|
+
* @param symbol - Trading pair symbol
|
|
50920
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50921
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50922
|
+
* @returns Promise resolving to PnL percentage or null
|
|
50923
|
+
*
|
|
50924
|
+
* @example
|
|
50925
|
+
* ```typescript
|
|
50926
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50927
|
+
* "BTCUSDT",
|
|
50928
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50929
|
+
* );
|
|
50930
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50931
|
+
* ```
|
|
50932
|
+
*/
|
|
50933
|
+
this.getPositionHighestPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50934
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, { symbol, context });
|
|
50935
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50936
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50937
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50938
|
+
{
|
|
50939
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50940
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50941
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50942
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50943
|
+
}
|
|
50944
|
+
return await backtest.strategyCoreService.getPositionHighestPnlPercentage(backtest$1, symbol, context);
|
|
50945
|
+
};
|
|
50946
|
+
/**
|
|
50947
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
50948
|
+
*
|
|
50949
|
+
* Returns null if no pending signal exists.
|
|
50950
|
+
*
|
|
50951
|
+
* @param symbol - Trading pair symbol
|
|
50952
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50953
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50954
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
50955
|
+
*
|
|
50956
|
+
* @example
|
|
50957
|
+
* ```typescript
|
|
50958
|
+
* const peakCost = await Reflect.getPositionHighestPnlCost(
|
|
50959
|
+
* "BTCUSDT",
|
|
50960
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50961
|
+
* );
|
|
50962
|
+
* console.log(`Peak PNL: $${peakCost}`);
|
|
50963
|
+
* ```
|
|
50964
|
+
*/
|
|
50965
|
+
this.getPositionHighestPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50966
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, { symbol, context });
|
|
50967
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50968
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50969
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50970
|
+
{
|
|
50971
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50972
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50973
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50974
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50975
|
+
}
|
|
50976
|
+
return await backtest.strategyCoreService.getPositionHighestPnlCost(backtest$1, symbol, context);
|
|
50977
|
+
};
|
|
50978
|
+
/**
|
|
50979
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
50980
|
+
*
|
|
50981
|
+
* Returns null if no pending signal exists.
|
|
50982
|
+
*
|
|
50983
|
+
* @param symbol - Trading pair symbol
|
|
50984
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50985
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50986
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
50987
|
+
*
|
|
50988
|
+
* @example
|
|
50989
|
+
* ```typescript
|
|
50990
|
+
* const wasReachable = await Reflect.getPositionHighestProfitBreakeven(
|
|
50991
|
+
* "BTCUSDT",
|
|
50992
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50993
|
+
* );
|
|
50994
|
+
* console.log(`Breakeven reachable at peak: ${wasReachable}`);
|
|
50995
|
+
* ```
|
|
50996
|
+
*/
|
|
50997
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context, backtest$1 = false) => {
|
|
50998
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, { symbol, context });
|
|
50999
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51000
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51001
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51002
|
+
{
|
|
51003
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51004
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51005
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
51006
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
51007
|
+
}
|
|
51008
|
+
return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
|
|
51009
|
+
};
|
|
51010
|
+
/**
|
|
51011
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
51012
|
+
*
|
|
51013
|
+
* Returns null if no pending signal exists.
|
|
51014
|
+
*
|
|
51015
|
+
* @param symbol - Trading pair symbol
|
|
51016
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51017
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51018
|
+
* @returns Promise resolving to minutes since highest profit price was recorded, or null
|
|
51019
|
+
*
|
|
51020
|
+
* @example
|
|
51021
|
+
* ```typescript
|
|
51022
|
+
* const minutes = await Reflect.getPositionDrawdownMinutes(
|
|
51023
|
+
* "BTCUSDT",
|
|
51024
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51025
|
+
* );
|
|
51026
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
51027
|
+
* ```
|
|
51028
|
+
*/
|
|
51029
|
+
this.getPositionDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
51030
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, { symbol, context });
|
|
51031
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51032
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51033
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51034
|
+
{
|
|
51035
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51036
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51037
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
51038
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
51039
|
+
}
|
|
51040
|
+
return await backtest.strategyCoreService.getPositionDrawdownMinutes(backtest$1, symbol, context);
|
|
51041
|
+
};
|
|
51042
|
+
/**
|
|
51043
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
51044
|
+
*
|
|
51045
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
51046
|
+
* pulling back from its peak profit level.
|
|
51047
|
+
* Returns null if no pending signal exists.
|
|
51048
|
+
*
|
|
51049
|
+
* @param symbol - Trading pair symbol
|
|
51050
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51051
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51052
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
51053
|
+
*
|
|
51054
|
+
* @example
|
|
51055
|
+
* ```typescript
|
|
51056
|
+
* const minutes = await Reflect.getPositionHighestProfitMinutes(
|
|
51057
|
+
* "BTCUSDT",
|
|
51058
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51059
|
+
* );
|
|
51060
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
51061
|
+
* ```
|
|
51062
|
+
*/
|
|
51063
|
+
this.getPositionHighestProfitMinutes = async (symbol, context, backtest$1 = false) => {
|
|
51064
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, { symbol, context });
|
|
51065
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51066
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51067
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51068
|
+
{
|
|
51069
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51070
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51071
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
51072
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
51073
|
+
}
|
|
51074
|
+
return await backtest.strategyCoreService.getPositionHighestProfitMinutes(backtest$1, symbol, context);
|
|
51075
|
+
};
|
|
51076
|
+
/**
|
|
51077
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
51078
|
+
*
|
|
51079
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
51080
|
+
* Zero when called at the exact moment the trough was set.
|
|
51081
|
+
* Returns null if no pending signal exists.
|
|
51082
|
+
*
|
|
51083
|
+
* @param symbol - Trading pair symbol
|
|
51084
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51085
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51086
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
51087
|
+
*
|
|
51088
|
+
* @example
|
|
51089
|
+
* ```typescript
|
|
51090
|
+
* const minutes = await Reflect.getPositionMaxDrawdownMinutes(
|
|
51091
|
+
* "BTCUSDT",
|
|
51092
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51093
|
+
* );
|
|
51094
|
+
* console.log(`Drawdown trough was ${minutes} minutes ago`);
|
|
51095
|
+
* ```
|
|
51096
|
+
*/
|
|
51097
|
+
this.getPositionMaxDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
51098
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, { symbol, context });
|
|
51099
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51100
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51101
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51102
|
+
{
|
|
51103
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51104
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51105
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
51106
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
51107
|
+
}
|
|
51108
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownMinutes(backtest$1, symbol, context);
|
|
51109
|
+
};
|
|
51110
|
+
/**
|
|
51111
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
51112
|
+
*
|
|
51113
|
+
* Returns null if no pending signal exists.
|
|
51114
|
+
*
|
|
51115
|
+
* @param symbol - Trading pair symbol
|
|
51116
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51117
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51118
|
+
* @returns Promise resolving to price or null
|
|
51119
|
+
*
|
|
51120
|
+
* @example
|
|
51121
|
+
* ```typescript
|
|
51122
|
+
* const troughPrice = await Reflect.getPositionMaxDrawdownPrice(
|
|
51123
|
+
* "BTCUSDT",
|
|
51124
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51125
|
+
* );
|
|
51126
|
+
* console.log(`Worst price: ${troughPrice}`);
|
|
51127
|
+
* ```
|
|
51128
|
+
*/
|
|
51129
|
+
this.getPositionMaxDrawdownPrice = async (symbol, context, backtest$1 = false) => {
|
|
51130
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, { symbol, context });
|
|
51131
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51132
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51133
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51134
|
+
{
|
|
51135
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51136
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51137
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
51138
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
51139
|
+
}
|
|
51140
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPrice(backtest$1, symbol, context);
|
|
51141
|
+
};
|
|
51142
|
+
/**
|
|
51143
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
51144
|
+
*
|
|
51145
|
+
* Returns null if no pending signal exists.
|
|
51146
|
+
*
|
|
51147
|
+
* @param symbol - Trading pair symbol
|
|
51148
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51149
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51150
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
51151
|
+
*
|
|
51152
|
+
* @example
|
|
51153
|
+
* ```typescript
|
|
51154
|
+
* const ts = await Reflect.getPositionMaxDrawdownTimestamp(
|
|
51155
|
+
* "BTCUSDT",
|
|
51156
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51157
|
+
* );
|
|
51158
|
+
* console.log(`Worst drawdown at: ${new Date(ts).toISOString()}`);
|
|
51159
|
+
* ```
|
|
51160
|
+
*/
|
|
51161
|
+
this.getPositionMaxDrawdownTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
51162
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, { symbol, context });
|
|
51163
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51164
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51165
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51166
|
+
{
|
|
51167
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51168
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51169
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
51170
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
51171
|
+
}
|
|
51172
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownTimestamp(backtest$1, symbol, context);
|
|
51173
|
+
};
|
|
51174
|
+
/**
|
|
51175
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
51176
|
+
*
|
|
51177
|
+
* Returns null if no pending signal exists.
|
|
51178
|
+
*
|
|
51179
|
+
* @param symbol - Trading pair symbol
|
|
51180
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51181
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51182
|
+
* @returns Promise resolving to PnL percentage or null
|
|
51183
|
+
*
|
|
51184
|
+
* @example
|
|
51185
|
+
* ```typescript
|
|
51186
|
+
* const worstPnl = await Reflect.getPositionMaxDrawdownPnlPercentage(
|
|
51187
|
+
* "BTCUSDT",
|
|
51188
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51189
|
+
* );
|
|
51190
|
+
* console.log(`Worst PNL: ${worstPnl}%`);
|
|
51191
|
+
* ```
|
|
51192
|
+
*/
|
|
51193
|
+
this.getPositionMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51194
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
51195
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51196
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51197
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51198
|
+
{
|
|
51199
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51200
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51201
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51202
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51203
|
+
}
|
|
51204
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
51205
|
+
};
|
|
51206
|
+
/**
|
|
51207
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
51208
|
+
*
|
|
51209
|
+
* Returns null if no pending signal exists.
|
|
51210
|
+
*
|
|
51211
|
+
* @param symbol - Trading pair symbol
|
|
51212
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51213
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51214
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
51215
|
+
*
|
|
51216
|
+
* @example
|
|
51217
|
+
* ```typescript
|
|
51218
|
+
* const worstCost = await Reflect.getPositionMaxDrawdownPnlCost(
|
|
51219
|
+
* "BTCUSDT",
|
|
51220
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51221
|
+
* );
|
|
51222
|
+
* console.log(`Worst PNL: $${worstCost}`);
|
|
51223
|
+
* ```
|
|
51224
|
+
*/
|
|
51225
|
+
this.getPositionMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51226
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
51227
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51228
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51229
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51230
|
+
{
|
|
51231
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51232
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51233
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
51234
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
51235
|
+
}
|
|
51236
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
51237
|
+
};
|
|
51238
|
+
/**
|
|
51239
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
51240
|
+
*
|
|
51241
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51242
|
+
*
|
|
51243
|
+
* @param symbol - Trading pair symbol
|
|
51244
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51245
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51246
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
51247
|
+
*
|
|
51248
|
+
* @example
|
|
51249
|
+
* ```typescript
|
|
51250
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
51251
|
+
* "BTCUSDT",
|
|
51252
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51253
|
+
* );
|
|
51254
|
+
* console.log(`Dropped ${distance}% from peak`);
|
|
51255
|
+
* ```
|
|
51256
|
+
*/
|
|
51257
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51258
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
51259
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51260
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51261
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51262
|
+
{
|
|
51263
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51264
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51265
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
51266
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
51267
|
+
}
|
|
51268
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(backtest$1, symbol, context);
|
|
51269
|
+
};
|
|
51270
|
+
/**
|
|
51271
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
51272
|
+
*
|
|
51273
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51274
|
+
*
|
|
51275
|
+
* @param symbol - Trading pair symbol
|
|
51276
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51277
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51278
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
51279
|
+
*
|
|
51280
|
+
* @example
|
|
51281
|
+
* ```typescript
|
|
51282
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlCost(
|
|
51283
|
+
* "BTCUSDT",
|
|
51284
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51285
|
+
* );
|
|
51286
|
+
* console.log(`Dropped $${distance} from peak`);
|
|
51287
|
+
* ```
|
|
51288
|
+
*/
|
|
51289
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51290
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, { symbol, context });
|
|
51291
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51292
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51293
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51294
|
+
{
|
|
51295
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51296
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51297
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
51298
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
51299
|
+
}
|
|
51300
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(backtest$1, symbol, context);
|
|
51301
|
+
};
|
|
51302
|
+
/**
|
|
51303
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
51304
|
+
*
|
|
51305
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51306
|
+
*
|
|
51307
|
+
* @param symbol - Trading pair symbol
|
|
51308
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51309
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51310
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL% (≥ 0) or null
|
|
51311
|
+
*
|
|
51312
|
+
* @example
|
|
51313
|
+
* ```typescript
|
|
51314
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlPercentage(
|
|
51315
|
+
* "BTCUSDT",
|
|
51316
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51317
|
+
* );
|
|
51318
|
+
* console.log(`${distance}% above worst trough`);
|
|
51319
|
+
* ```
|
|
51320
|
+
*/
|
|
51321
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51322
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
51323
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51324
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51325
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51326
|
+
{
|
|
51327
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51328
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51329
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51330
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51331
|
+
}
|
|
51332
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
51333
|
+
};
|
|
51334
|
+
/**
|
|
51335
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
51336
|
+
*
|
|
51337
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51338
|
+
*
|
|
51339
|
+
* @param symbol - Trading pair symbol
|
|
51340
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51341
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51342
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL cost (≥ 0) or null
|
|
51343
|
+
*
|
|
51344
|
+
* @example
|
|
51345
|
+
* ```typescript
|
|
51346
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlCost(
|
|
51347
|
+
* "BTCUSDT",
|
|
51348
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51349
|
+
* );
|
|
51350
|
+
* console.log(`$${distance} above worst trough`);
|
|
51351
|
+
* ```
|
|
51352
|
+
*/
|
|
51353
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51354
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
51355
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51356
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51357
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51358
|
+
{
|
|
51359
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51360
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51361
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
51362
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
51363
|
+
}
|
|
51364
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
51365
|
+
};
|
|
51366
|
+
/**
|
|
51367
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
51368
|
+
*
|
|
51369
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51370
|
+
*
|
|
51371
|
+
* @param symbol - Trading pair symbol
|
|
51372
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51373
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51374
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
51375
|
+
*
|
|
51376
|
+
* @example
|
|
51377
|
+
* ```typescript
|
|
51378
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlPercentage(
|
|
51379
|
+
* "BTCUSDT",
|
|
51380
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51381
|
+
* );
|
|
51382
|
+
* console.log(`Peak-to-trough: ${distance}%`);
|
|
51383
|
+
* ```
|
|
51384
|
+
*/
|
|
51385
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51386
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
51387
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51388
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51389
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51390
|
+
{
|
|
51391
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51392
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51393
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
51394
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
51395
|
+
}
|
|
51396
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(backtest$1, symbol, context);
|
|
51397
|
+
};
|
|
51398
|
+
/**
|
|
51399
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
51400
|
+
*
|
|
51401
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51402
|
+
*
|
|
51403
|
+
* @param symbol - Trading pair symbol
|
|
51404
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51405
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51406
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
51407
|
+
*
|
|
51408
|
+
* @example
|
|
51409
|
+
* ```typescript
|
|
51410
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlCost(
|
|
51411
|
+
* "BTCUSDT",
|
|
51412
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51413
|
+
* );
|
|
51414
|
+
* console.log(`Peak-to-trough: $${distance}`);
|
|
51415
|
+
* ```
|
|
51416
|
+
*/
|
|
51417
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51418
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, { symbol, context });
|
|
51419
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51420
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51421
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51422
|
+
{
|
|
51423
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51424
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51425
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
51426
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
51427
|
+
}
|
|
51428
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(backtest$1, symbol, context);
|
|
51429
|
+
};
|
|
51430
|
+
}
|
|
51431
|
+
}
|
|
51432
|
+
/**
|
|
51433
|
+
* Singleton instance of ReflectUtils for convenient position state queries.
|
|
51434
|
+
*
|
|
51435
|
+
* @example
|
|
51436
|
+
* ```typescript
|
|
51437
|
+
* import { Reflect } from "backtest-kit";
|
|
51438
|
+
*
|
|
51439
|
+
* // Real-time PNL
|
|
51440
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
51441
|
+
* "BTCUSDT",
|
|
51442
|
+
* 45000,
|
|
51443
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51444
|
+
* );
|
|
51445
|
+
* console.log(`PNL: ${pnl}%`);
|
|
51446
|
+
*
|
|
51447
|
+
* // Peak profit
|
|
51448
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
51449
|
+
* "BTCUSDT",
|
|
51450
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51451
|
+
* );
|
|
51452
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
51453
|
+
*
|
|
51454
|
+
* // Drawdown from peak
|
|
51455
|
+
* const drawdown = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
51456
|
+
* "BTCUSDT",
|
|
51457
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51458
|
+
* );
|
|
51459
|
+
* console.log(`Dropped ${drawdown}% from peak`);
|
|
51460
|
+
* ```
|
|
51461
|
+
*/
|
|
51462
|
+
const Reflect$1 = new ReflectUtils();
|
|
51463
|
+
|
|
49738
51464
|
/**
|
|
49739
51465
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
49740
51466
|
*
|
|
@@ -53662,12 +55388,15 @@ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
|
|
|
53662
55388
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
53663
55389
|
const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
|
|
53664
55390
|
const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
|
|
55391
|
+
const CACHE_METHOD_NAME_FN_HAS_VALUE = "CacheUtils.fn.hasValue";
|
|
53665
55392
|
const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
|
|
53666
55393
|
const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
55394
|
+
const CACHE_METHOD_NAME_FILE_HAS_VALUE = "CacheUtils.file.hasValue";
|
|
53667
55395
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
53668
55396
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
53669
55397
|
const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
|
|
53670
55398
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
55399
|
+
const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
|
|
53671
55400
|
const MS_PER_MINUTE$1 = 60000;
|
|
53672
55401
|
const INTERVAL_MINUTES$1 = {
|
|
53673
55402
|
"1m": 1,
|
|
@@ -53857,6 +55586,36 @@ class CacheFnInstance {
|
|
|
53857
55586
|
}
|
|
53858
55587
|
}
|
|
53859
55588
|
};
|
|
55589
|
+
/**
|
|
55590
|
+
* Check whether a valid (non-expired) cache entry exists for the current context and arguments.
|
|
55591
|
+
*
|
|
55592
|
+
* Returns `true` if a cached value exists and its interval is still current.
|
|
55593
|
+
* Returns `false` if there is no entry or the cached entry has expired.
|
|
55594
|
+
*
|
|
55595
|
+
* Requires active execution context and method context.
|
|
55596
|
+
*
|
|
55597
|
+
* @param args - Arguments to look up in the cache
|
|
55598
|
+
* @returns `true` if a fresh cached value exists, `false` otherwise
|
|
55599
|
+
*/
|
|
55600
|
+
this.hasValue = (...args) => {
|
|
55601
|
+
if (!MethodContextService.hasContext()) {
|
|
55602
|
+
throw new Error("CacheFnInstance hasValue requires method context");
|
|
55603
|
+
}
|
|
55604
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55605
|
+
throw new Error("CacheFnInstance hasValue requires execution context");
|
|
55606
|
+
}
|
|
55607
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
55608
|
+
const argKey = String(this.key(args));
|
|
55609
|
+
const key = `${contextKey}:${argKey}`;
|
|
55610
|
+
const cached = this._cacheMap.get(key);
|
|
55611
|
+
if (!cached) {
|
|
55612
|
+
return false;
|
|
55613
|
+
}
|
|
55614
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
55615
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
55616
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
55617
|
+
return currentAligned === cachedAligned;
|
|
55618
|
+
};
|
|
53860
55619
|
/**
|
|
53861
55620
|
* Garbage collect expired cache entries.
|
|
53862
55621
|
*
|
|
@@ -53981,6 +55740,33 @@ class CacheFileInstance {
|
|
|
53981
55740
|
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53982
55741
|
return result;
|
|
53983
55742
|
};
|
|
55743
|
+
/**
|
|
55744
|
+
* Check whether a cached value exists on disk for the given arguments and current interval.
|
|
55745
|
+
*
|
|
55746
|
+
* Returns `true` if a persisted record exists for the current aligned timestamp.
|
|
55747
|
+
* Returns `false` if no record is found.
|
|
55748
|
+
*
|
|
55749
|
+
* Requires active execution context and method context.
|
|
55750
|
+
*
|
|
55751
|
+
* @param args - Arguments forwarded to the key generator
|
|
55752
|
+
* @returns `true` if a cached record exists, `false` otherwise
|
|
55753
|
+
*/
|
|
55754
|
+
this.hasValue = async (...args) => {
|
|
55755
|
+
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
|
|
55756
|
+
if (!MethodContextService.hasContext()) {
|
|
55757
|
+
throw new Error("CacheFileInstance hasValue requires method context");
|
|
55758
|
+
}
|
|
55759
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55760
|
+
throw new Error("CacheFileInstance hasValue requires execution context");
|
|
55761
|
+
}
|
|
55762
|
+
const [symbol, ...rest] = args;
|
|
55763
|
+
const { when } = backtest.executionContextService.context;
|
|
55764
|
+
const alignedTs = align$1(when.getTime(), this.interval);
|
|
55765
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
55766
|
+
const entityKey = this.key([symbol, alignedTs, ...rest]);
|
|
55767
|
+
const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
|
|
55768
|
+
return cached !== null;
|
|
55769
|
+
};
|
|
53984
55770
|
/**
|
|
53985
55771
|
* Soft-delete all persisted records for this instance's bucket.
|
|
53986
55772
|
* After this call the next `run()` will recompute and re-cache the value.
|
|
@@ -54067,23 +55853,30 @@ class CacheUtils {
|
|
|
54067
55853
|
wrappedFn.clear = () => {
|
|
54068
55854
|
backtest.loggerService.info(CACHE_METHOD_NAME_FN_CLEAR);
|
|
54069
55855
|
if (!MethodContextService.hasContext()) {
|
|
54070
|
-
|
|
54071
|
-
return;
|
|
55856
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires method context`);
|
|
54072
55857
|
}
|
|
54073
55858
|
if (!ExecutionContextService.hasContext()) {
|
|
54074
|
-
|
|
54075
|
-
return;
|
|
55859
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires execution context`);
|
|
54076
55860
|
}
|
|
54077
55861
|
this._getFnInstance.get(run)?.clear();
|
|
54078
55862
|
};
|
|
54079
55863
|
wrappedFn.gc = () => {
|
|
54080
55864
|
backtest.loggerService.info(CACHE_METHOD_NAME_FN_GC);
|
|
54081
55865
|
if (!ExecutionContextService.hasContext()) {
|
|
54082
|
-
|
|
54083
|
-
return;
|
|
55866
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_GC} requires execution context`);
|
|
54084
55867
|
}
|
|
54085
55868
|
return this._getFnInstance.get(run)?.gc();
|
|
54086
55869
|
};
|
|
55870
|
+
wrappedFn.hasValue = (...args) => {
|
|
55871
|
+
backtest.loggerService.info(CACHE_METHOD_NAME_FN_HAS_VALUE);
|
|
55872
|
+
if (!MethodContextService.hasContext()) {
|
|
55873
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires method context`);
|
|
55874
|
+
}
|
|
55875
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55876
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
|
|
55877
|
+
}
|
|
55878
|
+
return this._getFnInstance.get(run)?.hasValue(...args) ?? false;
|
|
55879
|
+
};
|
|
54087
55880
|
return wrappedFn;
|
|
54088
55881
|
};
|
|
54089
55882
|
/**
|
|
@@ -54135,8 +55928,25 @@ class CacheUtils {
|
|
|
54135
55928
|
};
|
|
54136
55929
|
wrappedFn.clear = async () => {
|
|
54137
55930
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
|
|
55931
|
+
if (!MethodContextService.hasContext()) {
|
|
55932
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires method context`);
|
|
55933
|
+
}
|
|
55934
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55935
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires execution context`);
|
|
55936
|
+
}
|
|
54138
55937
|
await this._getFileInstance.get(run)?.clear();
|
|
54139
55938
|
};
|
|
55939
|
+
wrappedFn.hasValue = async (...args) => {
|
|
55940
|
+
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_HAS_VALUE);
|
|
55941
|
+
if (!MethodContextService.hasContext()) {
|
|
55942
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
|
|
55943
|
+
}
|
|
55944
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55945
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
|
|
55946
|
+
}
|
|
55947
|
+
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
55948
|
+
return await instance.hasValue(...args);
|
|
55949
|
+
};
|
|
54140
55950
|
return wrappedFn;
|
|
54141
55951
|
};
|
|
54142
55952
|
/**
|
|
@@ -54205,11 +56015,14 @@ const Cache = new CacheUtils();
|
|
|
54205
56015
|
|
|
54206
56016
|
const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
54207
56017
|
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
56018
|
+
const INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "IntervalFileInstance.hasValue";
|
|
54208
56019
|
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
54209
56020
|
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
54210
56021
|
const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
|
|
56022
|
+
const INTERVAL_METHOD_NAME_FN_HAS_VALUE = "IntervalUtils.fn.hasValue";
|
|
54211
56023
|
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
54212
56024
|
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
56025
|
+
const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
|
|
54213
56026
|
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
54214
56027
|
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
54215
56028
|
const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
|
|
@@ -54367,6 +56180,31 @@ class IntervalFnInstance {
|
|
|
54367
56180
|
}
|
|
54368
56181
|
}
|
|
54369
56182
|
};
|
|
56183
|
+
/**
|
|
56184
|
+
* Check whether the function has already fired for the current interval and context.
|
|
56185
|
+
*
|
|
56186
|
+
* Returns `true` if the function fired (non-null result) within the current interval boundary.
|
|
56187
|
+
* Returns `false` if there is no recorded firing for this interval.
|
|
56188
|
+
*
|
|
56189
|
+
* Requires active method context and execution context.
|
|
56190
|
+
*
|
|
56191
|
+
* @param args - Arguments to look up in the state map
|
|
56192
|
+
* @returns `true` if the function has already fired this interval, `false` otherwise
|
|
56193
|
+
*/
|
|
56194
|
+
this.hasValue = (...args) => {
|
|
56195
|
+
if (!MethodContextService.hasContext()) {
|
|
56196
|
+
throw new Error("IntervalFnInstance hasValue requires method context");
|
|
56197
|
+
}
|
|
56198
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56199
|
+
throw new Error("IntervalFnInstance hasValue requires execution context");
|
|
56200
|
+
}
|
|
56201
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
56202
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
56203
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
56204
|
+
const argKey = this.key(args);
|
|
56205
|
+
const stateKey = `${contextKey}:${argKey}`;
|
|
56206
|
+
return this._stateMap.get(stateKey) === currentAligned;
|
|
56207
|
+
};
|
|
54370
56208
|
/**
|
|
54371
56209
|
* Garbage collect expired state entries.
|
|
54372
56210
|
*
|
|
@@ -54488,6 +56326,33 @@ class IntervalFileInstance {
|
|
|
54488
56326
|
}
|
|
54489
56327
|
return result;
|
|
54490
56328
|
};
|
|
56329
|
+
/**
|
|
56330
|
+
* Check whether the function has already fired for the current interval on disk.
|
|
56331
|
+
*
|
|
56332
|
+
* Returns `true` if a persisted record exists for the current aligned timestamp.
|
|
56333
|
+
* Returns `false` if no record is found.
|
|
56334
|
+
*
|
|
56335
|
+
* Requires active execution context and method context.
|
|
56336
|
+
*
|
|
56337
|
+
* @param args - Arguments forwarded to the key generator
|
|
56338
|
+
* @returns `true` if a fired record exists, `false` otherwise
|
|
56339
|
+
*/
|
|
56340
|
+
this.hasValue = async (...args) => {
|
|
56341
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
|
|
56342
|
+
if (!MethodContextService.hasContext()) {
|
|
56343
|
+
throw new Error("IntervalFileInstance hasValue requires method context");
|
|
56344
|
+
}
|
|
56345
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56346
|
+
throw new Error("IntervalFileInstance hasValue requires execution context");
|
|
56347
|
+
}
|
|
56348
|
+
const [symbol, ...rest] = args;
|
|
56349
|
+
const { when } = backtest.executionContextService.context;
|
|
56350
|
+
const alignedMs = align(when.getTime(), this.interval);
|
|
56351
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
56352
|
+
const entityKey = this.key([symbol, alignedMs, ...rest]);
|
|
56353
|
+
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
56354
|
+
return cached !== null;
|
|
56355
|
+
};
|
|
54491
56356
|
/**
|
|
54492
56357
|
* Soft-delete all persisted records for this instance's bucket.
|
|
54493
56358
|
* After this call the function will fire again on the next `run()`.
|
|
@@ -54568,23 +56433,30 @@ class IntervalUtils {
|
|
|
54568
56433
|
wrappedFn.clear = () => {
|
|
54569
56434
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
54570
56435
|
if (!MethodContextService.hasContext()) {
|
|
54571
|
-
|
|
54572
|
-
return;
|
|
56436
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires method context`);
|
|
54573
56437
|
}
|
|
54574
56438
|
if (!ExecutionContextService.hasContext()) {
|
|
54575
|
-
|
|
54576
|
-
return;
|
|
56439
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires execution context`);
|
|
54577
56440
|
}
|
|
54578
56441
|
this._getInstance.get(run)?.clear();
|
|
54579
56442
|
};
|
|
54580
56443
|
wrappedFn.gc = () => {
|
|
54581
56444
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
|
|
54582
56445
|
if (!ExecutionContextService.hasContext()) {
|
|
54583
|
-
|
|
54584
|
-
return;
|
|
56446
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_GC} requires execution context`);
|
|
54585
56447
|
}
|
|
54586
56448
|
return this._getInstance.get(run)?.gc();
|
|
54587
56449
|
};
|
|
56450
|
+
wrappedFn.hasValue = (...args) => {
|
|
56451
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_HAS_VALUE);
|
|
56452
|
+
if (!MethodContextService.hasContext()) {
|
|
56453
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires method context`);
|
|
56454
|
+
}
|
|
56455
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56456
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
|
|
56457
|
+
}
|
|
56458
|
+
return this._getInstance.get(run)?.hasValue(...args) ?? false;
|
|
56459
|
+
};
|
|
54588
56460
|
return wrappedFn;
|
|
54589
56461
|
};
|
|
54590
56462
|
/**
|
|
@@ -54627,8 +56499,25 @@ class IntervalUtils {
|
|
|
54627
56499
|
};
|
|
54628
56500
|
wrappedFn.clear = async () => {
|
|
54629
56501
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
56502
|
+
if (!MethodContextService.hasContext()) {
|
|
56503
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires method context`);
|
|
56504
|
+
}
|
|
56505
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56506
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires execution context`);
|
|
56507
|
+
}
|
|
54630
56508
|
await this._getFileInstance.get(run)?.clear();
|
|
54631
56509
|
};
|
|
56510
|
+
wrappedFn.hasValue = async (...args) => {
|
|
56511
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_HAS_VALUE);
|
|
56512
|
+
if (!MethodContextService.hasContext()) {
|
|
56513
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
|
|
56514
|
+
}
|
|
56515
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56516
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
|
|
56517
|
+
}
|
|
56518
|
+
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
56519
|
+
return await instance.hasValue(...args);
|
|
56520
|
+
};
|
|
54632
56521
|
return wrappedFn;
|
|
54633
56522
|
};
|
|
54634
56523
|
/**
|
|
@@ -55802,4 +57691,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
55802
57691
|
return !errors.length;
|
|
55803
57692
|
};
|
|
55804
57693
|
|
|
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 };
|
|
57694
|
+
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, PersistRecentAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Recent, RecentBacktest, RecentLive, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Session, 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, getLatestSignal, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|