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.cjs
CHANGED
|
@@ -1002,6 +1002,12 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
|
|
|
1002
1002
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
|
|
1003
1003
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
|
|
1004
1004
|
const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
|
|
1005
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
|
|
1006
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
|
|
1007
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
|
|
1008
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON = "PersistRecentUtils.useJson";
|
|
1009
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY = "PersistRecentUtils.useDummy";
|
|
1010
|
+
const PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR = "PersistRecentUtils.clear";
|
|
1005
1011
|
const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
|
|
1006
1012
|
const BASE_UNLINK_RETRY_COUNT = 5;
|
|
1007
1013
|
const BASE_UNLINK_RETRY_DELAY = 1000;
|
|
@@ -2848,6 +2854,115 @@ class PersistMemoryUtils {
|
|
|
2848
2854
|
* ```
|
|
2849
2855
|
*/
|
|
2850
2856
|
const PersistMemoryAdapter = new PersistMemoryUtils();
|
|
2857
|
+
/**
|
|
2858
|
+
* Utility class for managing recent signal persistence.
|
|
2859
|
+
*
|
|
2860
|
+
* Features:
|
|
2861
|
+
* - Memoized storage instances per (symbol, strategyName, exchangeName, frameName) context
|
|
2862
|
+
* - Custom adapter support
|
|
2863
|
+
* - Atomic read/write operations
|
|
2864
|
+
* - Crash-safe recent signal state management
|
|
2865
|
+
*
|
|
2866
|
+
* Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
|
|
2867
|
+
*/
|
|
2868
|
+
class PersistRecentUtils {
|
|
2869
|
+
constructor() {
|
|
2870
|
+
this.PersistRecentFactory = PersistBase;
|
|
2871
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":"), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.PersistRecentFactory, [
|
|
2872
|
+
this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join("_"),
|
|
2873
|
+
`./dump/data/recent/`,
|
|
2874
|
+
]));
|
|
2875
|
+
/**
|
|
2876
|
+
* Reads the latest persisted recent signal for a given context.
|
|
2877
|
+
*
|
|
2878
|
+
* Returns null if no recent signal exists.
|
|
2879
|
+
*
|
|
2880
|
+
* @param symbol - Trading pair symbol
|
|
2881
|
+
* @param strategyName - Strategy identifier
|
|
2882
|
+
* @param exchangeName - Exchange identifier
|
|
2883
|
+
* @param frameName - Frame identifier
|
|
2884
|
+
* @returns Promise resolving to recent signal or null
|
|
2885
|
+
*/
|
|
2886
|
+
this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
2887
|
+
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
|
|
2888
|
+
const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
|
|
2889
|
+
const isInitial = !this.getStorage.has(key);
|
|
2890
|
+
const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
2891
|
+
await stateStorage.waitForInit(isInitial);
|
|
2892
|
+
if (await stateStorage.hasValue(symbol)) {
|
|
2893
|
+
return await stateStorage.readValue(symbol);
|
|
2894
|
+
}
|
|
2895
|
+
return null;
|
|
2896
|
+
};
|
|
2897
|
+
/**
|
|
2898
|
+
* Writes the latest recent signal to disk with atomic file writes.
|
|
2899
|
+
*
|
|
2900
|
+
* Uses symbol as the entity ID within the per-context storage instance.
|
|
2901
|
+
* Uses atomic writes to prevent corruption on crashes.
|
|
2902
|
+
*
|
|
2903
|
+
* @param signalRow - Recent signal data to persist
|
|
2904
|
+
* @param symbol - Trading pair symbol
|
|
2905
|
+
* @param strategyName - Strategy identifier
|
|
2906
|
+
* @param exchangeName - Exchange identifier
|
|
2907
|
+
* @param frameName - Frame identifier
|
|
2908
|
+
* @returns Promise that resolves when write is complete
|
|
2909
|
+
*/
|
|
2910
|
+
this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
2911
|
+
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
|
|
2912
|
+
const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
|
|
2913
|
+
const isInitial = !this.getStorage.has(key);
|
|
2914
|
+
const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
2915
|
+
await stateStorage.waitForInit(isInitial);
|
|
2916
|
+
await stateStorage.writeValue(symbol, signalRow);
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
createKeyParts(symbol, strategyName, exchangeName, frameName, backtest) {
|
|
2920
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
2921
|
+
if (frameName)
|
|
2922
|
+
parts.push(frameName);
|
|
2923
|
+
parts.push(backtest ? "backtest" : "live");
|
|
2924
|
+
return parts;
|
|
2925
|
+
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Registers a custom persistence adapter.
|
|
2928
|
+
*
|
|
2929
|
+
* @param Ctor - Custom PersistBase constructor
|
|
2930
|
+
*/
|
|
2931
|
+
usePersistRecentAdapter(Ctor) {
|
|
2932
|
+
LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
|
|
2933
|
+
this.PersistRecentFactory = Ctor;
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Clears the memoized storage cache.
|
|
2937
|
+
* Call this when process.cwd() changes between strategy iterations
|
|
2938
|
+
* so new storage instances are created with the updated base path.
|
|
2939
|
+
*/
|
|
2940
|
+
clear() {
|
|
2941
|
+
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
|
|
2942
|
+
this.getStorage.clear();
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Switches to the default JSON persist adapter.
|
|
2946
|
+
* All future persistence writes will use JSON storage.
|
|
2947
|
+
*/
|
|
2948
|
+
useJson() {
|
|
2949
|
+
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
|
|
2950
|
+
this.usePersistRecentAdapter(PersistBase);
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Switches to a dummy persist adapter that discards all writes.
|
|
2954
|
+
* All future persistence writes will be no-ops.
|
|
2955
|
+
*/
|
|
2956
|
+
useDummy() {
|
|
2957
|
+
LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
|
|
2958
|
+
this.usePersistRecentAdapter(PersistDummy);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Global singleton instance of PersistRecentUtils.
|
|
2963
|
+
* Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
|
|
2964
|
+
*/
|
|
2965
|
+
const PersistRecentAdapter = new PersistRecentUtils();
|
|
2851
2966
|
|
|
2852
2967
|
var _a$2, _b$2;
|
|
2853
2968
|
const BUSY_DELAY = 100;
|
|
@@ -6593,7 +6708,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
6593
6708
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
6594
6709
|
return result;
|
|
6595
6710
|
};
|
|
6596
|
-
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
|
|
6711
|
+
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId, cancelNote) => {
|
|
6597
6712
|
self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
|
|
6598
6713
|
symbol: self.params.execution.context.symbol,
|
|
6599
6714
|
signalId: scheduled.id,
|
|
@@ -6618,7 +6733,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
6618
6733
|
totalPartials: scheduled._partial?.length ?? 0,
|
|
6619
6734
|
originalPriceOpen: scheduled.priceOpen,
|
|
6620
6735
|
pnl: toProfitLossDto(scheduled, averagePrice),
|
|
6621
|
-
note: scheduled.note,
|
|
6736
|
+
note: cancelNote ?? scheduled.note,
|
|
6622
6737
|
});
|
|
6623
6738
|
}
|
|
6624
6739
|
await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6785,7 +6900,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
|
|
|
6785
6900
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
6786
6901
|
originalPriceOpen: closedSignal.priceOpen,
|
|
6787
6902
|
pnl: toProfitLossDto(closedSignal, averagePrice),
|
|
6788
|
-
note: closedSignal.note,
|
|
6903
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
6789
6904
|
});
|
|
6790
6905
|
await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
6791
6906
|
await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -6832,7 +6947,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6832
6947
|
if (self._cancelledSignal) {
|
|
6833
6948
|
// Сигнал был отменен через cancel() в onSchedulePing
|
|
6834
6949
|
const cancelId = self._cancelledSignal.cancelId;
|
|
6835
|
-
const
|
|
6950
|
+
const cancelNote = self._cancelledSignal.cancelNote;
|
|
6951
|
+
const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId, cancelNote);
|
|
6836
6952
|
return { outcome: "cancelled", result };
|
|
6837
6953
|
}
|
|
6838
6954
|
// КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
|
|
@@ -6886,7 +7002,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6886
7002
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
6887
7003
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
6888
7004
|
pnl: toProfitLossDto(activatedSignal, averagePrice),
|
|
6889
|
-
note: activatedSignal.note,
|
|
7005
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
6890
7006
|
});
|
|
6891
7007
|
return { outcome: "pending" };
|
|
6892
7008
|
}
|
|
@@ -6918,7 +7034,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6918
7034
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
6919
7035
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
6920
7036
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
6921
|
-
note: publicSignalForCommit.note,
|
|
7037
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
6922
7038
|
});
|
|
6923
7039
|
await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
|
|
6924
7040
|
await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
|
|
@@ -8039,6 +8155,44 @@ class ClientStrategy {
|
|
|
8039
8155
|
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8040
8156
|
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8041
8157
|
}
|
|
8158
|
+
/**
|
|
8159
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
8160
|
+
*
|
|
8161
|
+
* Measures the total swing from the stored `_peak.pnlPercentage` to the stored `_fall.pnlPercentage`.
|
|
8162
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
8163
|
+
*
|
|
8164
|
+
* Returns null if no pending signal exists.
|
|
8165
|
+
*
|
|
8166
|
+
* @param symbol - Trading pair symbol
|
|
8167
|
+
* @param currentPrice - Current market price
|
|
8168
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
8169
|
+
*/
|
|
8170
|
+
async getMaxDrawdownDistancePnlPercentage(symbol, currentPrice) {
|
|
8171
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlPercentage", { symbol, currentPrice });
|
|
8172
|
+
if (!this._pendingSignal) {
|
|
8173
|
+
return null;
|
|
8174
|
+
}
|
|
8175
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8176
|
+
}
|
|
8177
|
+
/**
|
|
8178
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
8179
|
+
*
|
|
8180
|
+
* Measures the total swing from the stored `_peak.pnlCost` to the stored `_fall.pnlCost`.
|
|
8181
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
8182
|
+
*
|
|
8183
|
+
* Returns null if no pending signal exists.
|
|
8184
|
+
*
|
|
8185
|
+
* @param symbol - Trading pair symbol
|
|
8186
|
+
* @param currentPrice - Current market price
|
|
8187
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
8188
|
+
*/
|
|
8189
|
+
async getMaxDrawdownDistancePnlCost(symbol, currentPrice) {
|
|
8190
|
+
this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlCost", { symbol, currentPrice });
|
|
8191
|
+
if (!this._pendingSignal) {
|
|
8192
|
+
return null;
|
|
8193
|
+
}
|
|
8194
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8195
|
+
}
|
|
8042
8196
|
/**
|
|
8043
8197
|
* Performs a single tick of strategy execution.
|
|
8044
8198
|
*
|
|
@@ -8103,7 +8257,7 @@ class ClientStrategy {
|
|
|
8103
8257
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8104
8258
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8105
8259
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8106
|
-
note: cancelledSignal.note,
|
|
8260
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8107
8261
|
});
|
|
8108
8262
|
// Call onCancel callback
|
|
8109
8263
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8157,7 +8311,7 @@ class ClientStrategy {
|
|
|
8157
8311
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8158
8312
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8159
8313
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8160
|
-
note: closedSignal.note,
|
|
8314
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8161
8315
|
});
|
|
8162
8316
|
// Call onClose callback
|
|
8163
8317
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8239,7 +8393,7 @@ class ClientStrategy {
|
|
|
8239
8393
|
totalPartials: activatedSignal._partial?.length ?? 0,
|
|
8240
8394
|
originalPriceOpen: activatedSignal.priceOpen,
|
|
8241
8395
|
pnl: toProfitLossDto(activatedSignal, currentPrice),
|
|
8242
|
-
note: activatedSignal.note,
|
|
8396
|
+
note: activatedSignal.activateNote ?? activatedSignal.note,
|
|
8243
8397
|
});
|
|
8244
8398
|
return await RETURN_IDLE_FN(this, currentPrice);
|
|
8245
8399
|
}
|
|
@@ -8270,7 +8424,7 @@ class ClientStrategy {
|
|
|
8270
8424
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
8271
8425
|
totalEntries: publicSignalForCommit.totalEntries,
|
|
8272
8426
|
totalPartials: publicSignalForCommit.totalPartials,
|
|
8273
|
-
note: publicSignalForCommit.note,
|
|
8427
|
+
note: activatedSignal.activateNote ?? publicSignalForCommit.note,
|
|
8274
8428
|
});
|
|
8275
8429
|
// Call onOpen callback
|
|
8276
8430
|
await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -8406,7 +8560,7 @@ class ClientStrategy {
|
|
|
8406
8560
|
totalPartials: cancelledSignal._partial?.length ?? 0,
|
|
8407
8561
|
originalPriceOpen: cancelledSignal.priceOpen,
|
|
8408
8562
|
pnl: toProfitLossDto(cancelledSignal, currentPrice),
|
|
8409
|
-
note: cancelledSignal.note,
|
|
8563
|
+
note: cancelledSignal.cancelNote ?? cancelledSignal.note,
|
|
8410
8564
|
});
|
|
8411
8565
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8412
8566
|
const cancelledResult = {
|
|
@@ -8461,7 +8615,7 @@ class ClientStrategy {
|
|
|
8461
8615
|
totalPartials: closedSignal._partial?.length ?? 0,
|
|
8462
8616
|
originalPriceOpen: closedSignal.priceOpen,
|
|
8463
8617
|
pnl: toProfitLossDto(closedSignal, currentPrice),
|
|
8464
|
-
note: closedSignal.note,
|
|
8618
|
+
note: closedSignal.closeNote ?? closedSignal.note,
|
|
8465
8619
|
});
|
|
8466
8620
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
8467
8621
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
@@ -8645,7 +8799,8 @@ class ClientStrategy {
|
|
|
8645
8799
|
* // Strategy continues, can generate new signals
|
|
8646
8800
|
* ```
|
|
8647
8801
|
*/
|
|
8648
|
-
async cancelScheduled(symbol, backtest,
|
|
8802
|
+
async cancelScheduled(symbol, backtest, payload) {
|
|
8803
|
+
const cancelId = payload.id;
|
|
8649
8804
|
this.params.logger.debug("ClientStrategy cancelScheduled", {
|
|
8650
8805
|
symbol,
|
|
8651
8806
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8657,6 +8812,7 @@ class ClientStrategy {
|
|
|
8657
8812
|
if (this._scheduledSignal) {
|
|
8658
8813
|
this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
|
|
8659
8814
|
cancelId,
|
|
8815
|
+
cancelNote: payload.note,
|
|
8660
8816
|
});
|
|
8661
8817
|
this._scheduledSignal = null;
|
|
8662
8818
|
}
|
|
@@ -8688,7 +8844,8 @@ class ClientStrategy {
|
|
|
8688
8844
|
* // Scheduled signal becomes pending signal immediately
|
|
8689
8845
|
* ```
|
|
8690
8846
|
*/
|
|
8691
|
-
async activateScheduled(symbol, backtest,
|
|
8847
|
+
async activateScheduled(symbol, backtest, payload) {
|
|
8848
|
+
const activateId = payload.id;
|
|
8692
8849
|
this.params.logger.debug("ClientStrategy activateScheduled", {
|
|
8693
8850
|
symbol,
|
|
8694
8851
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
@@ -8706,6 +8863,7 @@ class ClientStrategy {
|
|
|
8706
8863
|
if (this._scheduledSignal) {
|
|
8707
8864
|
this._activatedSignal = Object.assign({}, this._scheduledSignal, {
|
|
8708
8865
|
activateId,
|
|
8866
|
+
activateNote: payload.note,
|
|
8709
8867
|
});
|
|
8710
8868
|
this._scheduledSignal = null;
|
|
8711
8869
|
}
|
|
@@ -8737,7 +8895,8 @@ class ClientStrategy {
|
|
|
8737
8895
|
* // Strategy continues, can generate new signals
|
|
8738
8896
|
* ```
|
|
8739
8897
|
*/
|
|
8740
|
-
async closePending(symbol, backtest,
|
|
8898
|
+
async closePending(symbol, backtest, payload) {
|
|
8899
|
+
const closeId = payload.id;
|
|
8741
8900
|
this.params.logger.debug("ClientStrategy closePending", {
|
|
8742
8901
|
symbol,
|
|
8743
8902
|
hasPendingSignal: this._pendingSignal !== null,
|
|
@@ -8748,6 +8907,7 @@ class ClientStrategy {
|
|
|
8748
8907
|
if (this._pendingSignal) {
|
|
8749
8908
|
this._closedSignal = Object.assign({}, this._pendingSignal, {
|
|
8750
8909
|
closeId,
|
|
8910
|
+
closeNote: payload.note,
|
|
8751
8911
|
});
|
|
8752
8912
|
this._pendingSignal = null;
|
|
8753
8913
|
}
|
|
@@ -10130,7 +10290,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
10130
10290
|
* @param backtest - Whether running in backtest mode
|
|
10131
10291
|
* @returns Unique string key for memoization
|
|
10132
10292
|
*/
|
|
10133
|
-
const CREATE_KEY_FN$
|
|
10293
|
+
const CREATE_KEY_FN$u = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10134
10294
|
const parts = [symbol, strategyName, exchangeName];
|
|
10135
10295
|
if (frameName)
|
|
10136
10296
|
parts.push(frameName);
|
|
@@ -10397,7 +10557,7 @@ class StrategyConnectionService {
|
|
|
10397
10557
|
* @param backtest - Whether running in backtest mode
|
|
10398
10558
|
* @returns Configured ClientStrategy instance
|
|
10399
10559
|
*/
|
|
10400
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10560
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10401
10561
|
const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10402
10562
|
return new ClientStrategy({
|
|
10403
10563
|
symbol,
|
|
@@ -11238,6 +11398,48 @@ class StrategyConnectionService {
|
|
|
11238
11398
|
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11239
11399
|
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11240
11400
|
};
|
|
11401
|
+
/**
|
|
11402
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
11403
|
+
*
|
|
11404
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11405
|
+
* ClientStrategy.getMaxDrawdownDistancePnlPercentage().
|
|
11406
|
+
* Returns null if no pending signal exists.
|
|
11407
|
+
*
|
|
11408
|
+
* @param backtest - Whether running in backtest mode
|
|
11409
|
+
* @param symbol - Trading pair symbol
|
|
11410
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11411
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
11412
|
+
*/
|
|
11413
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11414
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlPercentage", {
|
|
11415
|
+
symbol,
|
|
11416
|
+
context,
|
|
11417
|
+
});
|
|
11418
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11419
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11420
|
+
return await strategy.getMaxDrawdownDistancePnlPercentage(symbol, currentPrice);
|
|
11421
|
+
};
|
|
11422
|
+
/**
|
|
11423
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
11424
|
+
*
|
|
11425
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11426
|
+
* ClientStrategy.getMaxDrawdownDistancePnlCost().
|
|
11427
|
+
* Returns null if no pending signal exists.
|
|
11428
|
+
*
|
|
11429
|
+
* @param backtest - Whether running in backtest mode
|
|
11430
|
+
* @param symbol - Trading pair symbol
|
|
11431
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11432
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
11433
|
+
*/
|
|
11434
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
11435
|
+
this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlCost", {
|
|
11436
|
+
symbol,
|
|
11437
|
+
context,
|
|
11438
|
+
});
|
|
11439
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11440
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11441
|
+
return await strategy.getMaxDrawdownDistancePnlCost(symbol, currentPrice);
|
|
11442
|
+
};
|
|
11241
11443
|
/**
|
|
11242
11444
|
* Disposes the ClientStrategy instance for the given context.
|
|
11243
11445
|
*
|
|
@@ -11276,7 +11478,7 @@ class StrategyConnectionService {
|
|
|
11276
11478
|
}
|
|
11277
11479
|
return;
|
|
11278
11480
|
}
|
|
11279
|
-
const key = CREATE_KEY_FN$
|
|
11481
|
+
const key = CREATE_KEY_FN$u(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
11280
11482
|
if (!this.getStrategy.has(key)) {
|
|
11281
11483
|
return;
|
|
11282
11484
|
}
|
|
@@ -11297,17 +11499,17 @@ class StrategyConnectionService {
|
|
|
11297
11499
|
* @param backtest - Whether running in backtest mode
|
|
11298
11500
|
* @param symbol - Trading pair symbol
|
|
11299
11501
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
11300
|
-
* @param
|
|
11502
|
+
* @param payload - Optional commit payload with id and note
|
|
11301
11503
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
11302
11504
|
*/
|
|
11303
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
11505
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11304
11506
|
this.loggerService.log("strategyConnectionService cancelScheduled", {
|
|
11305
11507
|
symbol,
|
|
11306
11508
|
context,
|
|
11307
|
-
|
|
11509
|
+
payload,
|
|
11308
11510
|
});
|
|
11309
11511
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11310
|
-
await strategy.cancelScheduled(symbol, backtest,
|
|
11512
|
+
await strategy.cancelScheduled(symbol, backtest, payload);
|
|
11311
11513
|
};
|
|
11312
11514
|
/**
|
|
11313
11515
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -11322,17 +11524,17 @@ class StrategyConnectionService {
|
|
|
11322
11524
|
* @param backtest - Whether running in backtest mode
|
|
11323
11525
|
* @param symbol - Trading pair symbol
|
|
11324
11526
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
11325
|
-
* @param
|
|
11527
|
+
* @param payload - Optional commit payload with id and note
|
|
11326
11528
|
* @returns Promise that resolves when pending signal is closed
|
|
11327
11529
|
*/
|
|
11328
|
-
this.closePending = async (backtest, symbol, context,
|
|
11530
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
11329
11531
|
this.loggerService.log("strategyConnectionService closePending", {
|
|
11330
11532
|
symbol,
|
|
11331
11533
|
context,
|
|
11332
|
-
|
|
11534
|
+
payload,
|
|
11333
11535
|
});
|
|
11334
11536
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11335
|
-
await strategy.closePending(symbol, backtest,
|
|
11537
|
+
await strategy.closePending(symbol, backtest, payload);
|
|
11336
11538
|
};
|
|
11337
11539
|
/**
|
|
11338
11540
|
* Checks whether `partialProfit` would succeed without executing it.
|
|
@@ -11611,7 +11813,7 @@ class StrategyConnectionService {
|
|
|
11611
11813
|
* @param backtest - Whether running in backtest mode
|
|
11612
11814
|
* @param symbol - Trading pair symbol
|
|
11613
11815
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11614
|
-
* @param
|
|
11816
|
+
* @param payload - Optional commit payload with id and note
|
|
11615
11817
|
* @returns Promise that resolves when activation flag is set
|
|
11616
11818
|
*
|
|
11617
11819
|
* @example
|
|
@@ -11621,19 +11823,19 @@ class StrategyConnectionService {
|
|
|
11621
11823
|
* false,
|
|
11622
11824
|
* "BTCUSDT",
|
|
11623
11825
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
11624
|
-
* "manual-activation"
|
|
11826
|
+
* { id: "manual-activation" }
|
|
11625
11827
|
* );
|
|
11626
11828
|
* ```
|
|
11627
11829
|
*/
|
|
11628
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
11830
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
11629
11831
|
this.loggerService.log("strategyConnectionService activateScheduled", {
|
|
11630
11832
|
symbol,
|
|
11631
11833
|
context,
|
|
11632
11834
|
backtest,
|
|
11633
|
-
|
|
11835
|
+
payload,
|
|
11634
11836
|
});
|
|
11635
11837
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11636
|
-
return await strategy.activateScheduled(symbol, backtest,
|
|
11838
|
+
return await strategy.activateScheduled(symbol, backtest, payload);
|
|
11637
11839
|
};
|
|
11638
11840
|
/**
|
|
11639
11841
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -12445,7 +12647,7 @@ class ClientRisk {
|
|
|
12445
12647
|
* @param backtest - Whether running in backtest mode
|
|
12446
12648
|
* @returns Unique string key for memoization
|
|
12447
12649
|
*/
|
|
12448
|
-
const CREATE_KEY_FN$
|
|
12650
|
+
const CREATE_KEY_FN$t = (riskName, exchangeName, frameName, backtest) => {
|
|
12449
12651
|
const parts = [riskName, exchangeName];
|
|
12450
12652
|
if (frameName)
|
|
12451
12653
|
parts.push(frameName);
|
|
@@ -12545,7 +12747,7 @@ class RiskConnectionService {
|
|
|
12545
12747
|
* @param backtest - True if backtest mode, false if live mode
|
|
12546
12748
|
* @returns Configured ClientRisk instance
|
|
12547
12749
|
*/
|
|
12548
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12750
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
12549
12751
|
const schema = this.riskSchemaService.get(riskName);
|
|
12550
12752
|
return new ClientRisk({
|
|
12551
12753
|
...schema,
|
|
@@ -12614,7 +12816,7 @@ class RiskConnectionService {
|
|
|
12614
12816
|
payload,
|
|
12615
12817
|
});
|
|
12616
12818
|
if (payload) {
|
|
12617
|
-
const key = CREATE_KEY_FN$
|
|
12819
|
+
const key = CREATE_KEY_FN$t(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12618
12820
|
this.getRisk.clear(key);
|
|
12619
12821
|
}
|
|
12620
12822
|
else {
|
|
@@ -13658,7 +13860,7 @@ class ClientAction {
|
|
|
13658
13860
|
* @param backtest - Whether running in backtest mode
|
|
13659
13861
|
* @returns Unique string key for memoization
|
|
13660
13862
|
*/
|
|
13661
|
-
const CREATE_KEY_FN$
|
|
13863
|
+
const CREATE_KEY_FN$s = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13662
13864
|
const parts = [actionName, strategyName, exchangeName];
|
|
13663
13865
|
if (frameName)
|
|
13664
13866
|
parts.push(frameName);
|
|
@@ -13710,7 +13912,7 @@ class ActionConnectionService {
|
|
|
13710
13912
|
* @param backtest - True if backtest mode, false if live mode
|
|
13711
13913
|
* @returns Configured ClientAction instance
|
|
13712
13914
|
*/
|
|
13713
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
13915
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13714
13916
|
const schema = this.actionSchemaService.get(actionName);
|
|
13715
13917
|
return new ClientAction({
|
|
13716
13918
|
...schema,
|
|
@@ -13921,7 +14123,7 @@ class ActionConnectionService {
|
|
|
13921
14123
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
13922
14124
|
return;
|
|
13923
14125
|
}
|
|
13924
|
-
const key = CREATE_KEY_FN$
|
|
14126
|
+
const key = CREATE_KEY_FN$s(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13925
14127
|
if (!this.getAction.has(key)) {
|
|
13926
14128
|
return;
|
|
13927
14129
|
}
|
|
@@ -13939,7 +14141,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
13939
14141
|
* @param exchangeName - Exchange name
|
|
13940
14142
|
* @returns Unique string key for memoization
|
|
13941
14143
|
*/
|
|
13942
|
-
const CREATE_KEY_FN$
|
|
14144
|
+
const CREATE_KEY_FN$r = (exchangeName) => {
|
|
13943
14145
|
return exchangeName;
|
|
13944
14146
|
};
|
|
13945
14147
|
/**
|
|
@@ -13963,7 +14165,7 @@ class ExchangeCoreService {
|
|
|
13963
14165
|
* @param exchangeName - Name of the exchange to validate
|
|
13964
14166
|
* @returns Promise that resolves when validation is complete
|
|
13965
14167
|
*/
|
|
13966
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
14168
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$r(exchangeName), async (exchangeName) => {
|
|
13967
14169
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
13968
14170
|
exchangeName,
|
|
13969
14171
|
});
|
|
@@ -14215,7 +14417,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
14215
14417
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14216
14418
|
* @returns Unique string key for memoization
|
|
14217
14419
|
*/
|
|
14218
|
-
const CREATE_KEY_FN$
|
|
14420
|
+
const CREATE_KEY_FN$q = (context) => {
|
|
14219
14421
|
const parts = [context.strategyName, context.exchangeName];
|
|
14220
14422
|
if (context.frameName)
|
|
14221
14423
|
parts.push(context.frameName);
|
|
@@ -14247,7 +14449,7 @@ class StrategyCoreService {
|
|
|
14247
14449
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14248
14450
|
* @returns Promise that resolves when validation is complete
|
|
14249
14451
|
*/
|
|
14250
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
14452
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
|
|
14251
14453
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
14252
14454
|
context,
|
|
14253
14455
|
});
|
|
@@ -14668,18 +14870,18 @@ class StrategyCoreService {
|
|
|
14668
14870
|
* @param backtest - Whether running in backtest mode
|
|
14669
14871
|
* @param symbol - Trading pair symbol
|
|
14670
14872
|
* @param ctx - Context with strategyName, exchangeName, frameName
|
|
14671
|
-
* @param
|
|
14873
|
+
* @param payload - Optional commit payload with id and note
|
|
14672
14874
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
14673
14875
|
*/
|
|
14674
|
-
this.cancelScheduled = async (backtest, symbol, context,
|
|
14876
|
+
this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
14675
14877
|
this.loggerService.log("strategyCoreService cancelScheduled", {
|
|
14676
14878
|
symbol,
|
|
14677
14879
|
context,
|
|
14678
14880
|
backtest,
|
|
14679
|
-
|
|
14881
|
+
payload,
|
|
14680
14882
|
});
|
|
14681
14883
|
await this.validate(context);
|
|
14682
|
-
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context,
|
|
14884
|
+
return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, payload);
|
|
14683
14885
|
};
|
|
14684
14886
|
/**
|
|
14685
14887
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -14695,18 +14897,18 @@ class StrategyCoreService {
|
|
|
14695
14897
|
* @param backtest - Whether running in backtest mode
|
|
14696
14898
|
* @param symbol - Trading pair symbol
|
|
14697
14899
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
14698
|
-
* @param
|
|
14900
|
+
* @param payload - Optional commit payload with id and note
|
|
14699
14901
|
* @returns Promise that resolves when pending signal is closed
|
|
14700
14902
|
*/
|
|
14701
|
-
this.closePending = async (backtest, symbol, context,
|
|
14903
|
+
this.closePending = async (backtest, symbol, context, payload = {}) => {
|
|
14702
14904
|
this.loggerService.log("strategyCoreService closePending", {
|
|
14703
14905
|
symbol,
|
|
14704
14906
|
context,
|
|
14705
14907
|
backtest,
|
|
14706
|
-
|
|
14908
|
+
payload,
|
|
14707
14909
|
});
|
|
14708
14910
|
await this.validate(context);
|
|
14709
|
-
return await this.strategyConnectionService.closePending(backtest, symbol, context,
|
|
14911
|
+
return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
|
|
14710
14912
|
};
|
|
14711
14913
|
/**
|
|
14712
14914
|
* Disposes the ClientStrategy instance for the given context.
|
|
@@ -15051,7 +15253,7 @@ class StrategyCoreService {
|
|
|
15051
15253
|
* @param backtest - Whether running in backtest mode
|
|
15052
15254
|
* @param symbol - Trading pair symbol
|
|
15053
15255
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15054
|
-
* @param
|
|
15256
|
+
* @param payload - Optional commit payload with id and note
|
|
15055
15257
|
* @returns Promise that resolves when activation flag is set
|
|
15056
15258
|
*
|
|
15057
15259
|
* @example
|
|
@@ -15061,19 +15263,19 @@ class StrategyCoreService {
|
|
|
15061
15263
|
* false,
|
|
15062
15264
|
* "BTCUSDT",
|
|
15063
15265
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
|
|
15064
|
-
* "manual-activation"
|
|
15266
|
+
* { id: "manual-activation" }
|
|
15065
15267
|
* );
|
|
15066
15268
|
* ```
|
|
15067
15269
|
*/
|
|
15068
|
-
this.activateScheduled = async (backtest, symbol, context,
|
|
15270
|
+
this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
|
|
15069
15271
|
this.loggerService.log("strategyCoreService activateScheduled", {
|
|
15070
15272
|
symbol,
|
|
15071
15273
|
context,
|
|
15072
15274
|
backtest,
|
|
15073
|
-
|
|
15275
|
+
payload,
|
|
15074
15276
|
});
|
|
15075
15277
|
await this.validate(context);
|
|
15076
|
-
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context,
|
|
15278
|
+
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, payload);
|
|
15077
15279
|
};
|
|
15078
15280
|
/**
|
|
15079
15281
|
* Checks whether `averageBuy` would succeed without executing it.
|
|
@@ -15475,6 +15677,44 @@ class StrategyCoreService {
|
|
|
15475
15677
|
await this.validate(context);
|
|
15476
15678
|
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15477
15679
|
};
|
|
15680
|
+
/**
|
|
15681
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
15682
|
+
*
|
|
15683
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlPercentage().
|
|
15684
|
+
* Returns null if no pending signal exists.
|
|
15685
|
+
*
|
|
15686
|
+
* @param backtest - Whether running in backtest mode
|
|
15687
|
+
* @param symbol - Trading pair symbol
|
|
15688
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15689
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
15690
|
+
*/
|
|
15691
|
+
this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15692
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlPercentage", {
|
|
15693
|
+
symbol,
|
|
15694
|
+
context,
|
|
15695
|
+
});
|
|
15696
|
+
await this.validate(context);
|
|
15697
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlPercentage(backtest, symbol, context);
|
|
15698
|
+
};
|
|
15699
|
+
/**
|
|
15700
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
15701
|
+
*
|
|
15702
|
+
* Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlCost().
|
|
15703
|
+
* Returns null if no pending signal exists.
|
|
15704
|
+
*
|
|
15705
|
+
* @param backtest - Whether running in backtest mode
|
|
15706
|
+
* @param symbol - Trading pair symbol
|
|
15707
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15708
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
15709
|
+
*/
|
|
15710
|
+
this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
|
|
15711
|
+
this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlCost", {
|
|
15712
|
+
symbol,
|
|
15713
|
+
context,
|
|
15714
|
+
});
|
|
15715
|
+
await this.validate(context);
|
|
15716
|
+
return await this.strategyConnectionService.getMaxDrawdownDistancePnlCost(backtest, symbol, context);
|
|
15717
|
+
};
|
|
15478
15718
|
}
|
|
15479
15719
|
}
|
|
15480
15720
|
|
|
@@ -15547,7 +15787,7 @@ class SizingGlobalService {
|
|
|
15547
15787
|
* @param context - Context with riskName, exchangeName, frameName
|
|
15548
15788
|
* @returns Unique string key for memoization
|
|
15549
15789
|
*/
|
|
15550
|
-
const CREATE_KEY_FN$
|
|
15790
|
+
const CREATE_KEY_FN$p = (context) => {
|
|
15551
15791
|
const parts = [context.riskName, context.exchangeName];
|
|
15552
15792
|
if (context.frameName)
|
|
15553
15793
|
parts.push(context.frameName);
|
|
@@ -15573,7 +15813,7 @@ class RiskGlobalService {
|
|
|
15573
15813
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
15574
15814
|
* @returns Promise that resolves when validation is complete
|
|
15575
15815
|
*/
|
|
15576
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
15816
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
|
|
15577
15817
|
this.loggerService.log("riskGlobalService validate", {
|
|
15578
15818
|
context,
|
|
15579
15819
|
});
|
|
@@ -15651,7 +15891,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
15651
15891
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15652
15892
|
* @returns Unique string key for memoization
|
|
15653
15893
|
*/
|
|
15654
|
-
const CREATE_KEY_FN$
|
|
15894
|
+
const CREATE_KEY_FN$o = (context) => {
|
|
15655
15895
|
const parts = [context.strategyName, context.exchangeName];
|
|
15656
15896
|
if (context.frameName)
|
|
15657
15897
|
parts.push(context.frameName);
|
|
@@ -15695,7 +15935,7 @@ class ActionCoreService {
|
|
|
15695
15935
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
15696
15936
|
* @returns Promise that resolves when all validations complete
|
|
15697
15937
|
*/
|
|
15698
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
15938
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
|
|
15699
15939
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
15700
15940
|
context,
|
|
15701
15941
|
});
|
|
@@ -20738,7 +20978,7 @@ const ReportWriter = new ReportWriterAdapter();
|
|
|
20738
20978
|
* @param backtest - Whether running in backtest mode
|
|
20739
20979
|
* @returns Unique string key for memoization
|
|
20740
20980
|
*/
|
|
20741
|
-
const CREATE_KEY_FN$
|
|
20981
|
+
const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20742
20982
|
const parts = [symbol, strategyName, exchangeName];
|
|
20743
20983
|
if (frameName)
|
|
20744
20984
|
parts.push(frameName);
|
|
@@ -20984,7 +21224,7 @@ class BacktestMarkdownService {
|
|
|
20984
21224
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20985
21225
|
* Each combination gets its own isolated storage instance.
|
|
20986
21226
|
*/
|
|
20987
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21227
|
+
this.getStorage = functoolsKit.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));
|
|
20988
21228
|
/**
|
|
20989
21229
|
* Processes tick events and accumulates closed signals.
|
|
20990
21230
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -21141,7 +21381,7 @@ class BacktestMarkdownService {
|
|
|
21141
21381
|
payload,
|
|
21142
21382
|
});
|
|
21143
21383
|
if (payload) {
|
|
21144
|
-
const key = CREATE_KEY_FN$
|
|
21384
|
+
const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21145
21385
|
this.getStorage.clear(key);
|
|
21146
21386
|
}
|
|
21147
21387
|
else {
|
|
@@ -21203,7 +21443,7 @@ class BacktestMarkdownService {
|
|
|
21203
21443
|
* @param backtest - Whether running in backtest mode
|
|
21204
21444
|
* @returns Unique string key for memoization
|
|
21205
21445
|
*/
|
|
21206
|
-
const CREATE_KEY_FN$
|
|
21446
|
+
const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21207
21447
|
const parts = [symbol, strategyName, exchangeName];
|
|
21208
21448
|
if (frameName)
|
|
21209
21449
|
parts.push(frameName);
|
|
@@ -21698,7 +21938,7 @@ class LiveMarkdownService {
|
|
|
21698
21938
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21699
21939
|
* Each combination gets its own isolated storage instance.
|
|
21700
21940
|
*/
|
|
21701
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21941
|
+
this.getStorage = functoolsKit.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));
|
|
21702
21942
|
/**
|
|
21703
21943
|
* Subscribes to live signal emitter to receive tick events.
|
|
21704
21944
|
* Protected against multiple subscriptions.
|
|
@@ -21916,7 +22156,7 @@ class LiveMarkdownService {
|
|
|
21916
22156
|
payload,
|
|
21917
22157
|
});
|
|
21918
22158
|
if (payload) {
|
|
21919
|
-
const key = CREATE_KEY_FN$
|
|
22159
|
+
const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21920
22160
|
this.getStorage.clear(key);
|
|
21921
22161
|
}
|
|
21922
22162
|
else {
|
|
@@ -21936,7 +22176,7 @@ class LiveMarkdownService {
|
|
|
21936
22176
|
* @param backtest - Whether running in backtest mode
|
|
21937
22177
|
* @returns Unique string key for memoization
|
|
21938
22178
|
*/
|
|
21939
|
-
const CREATE_KEY_FN$
|
|
22179
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21940
22180
|
const parts = [symbol, strategyName, exchangeName];
|
|
21941
22181
|
if (frameName)
|
|
21942
22182
|
parts.push(frameName);
|
|
@@ -22225,7 +22465,7 @@ class ScheduleMarkdownService {
|
|
|
22225
22465
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22226
22466
|
* Each combination gets its own isolated storage instance.
|
|
22227
22467
|
*/
|
|
22228
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22468
|
+
this.getStorage = functoolsKit.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));
|
|
22229
22469
|
/**
|
|
22230
22470
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
22231
22471
|
* Protected against multiple subscriptions.
|
|
@@ -22428,7 +22668,7 @@ class ScheduleMarkdownService {
|
|
|
22428
22668
|
payload,
|
|
22429
22669
|
});
|
|
22430
22670
|
if (payload) {
|
|
22431
|
-
const key = CREATE_KEY_FN$
|
|
22671
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22432
22672
|
this.getStorage.clear(key);
|
|
22433
22673
|
}
|
|
22434
22674
|
else {
|
|
@@ -22448,7 +22688,7 @@ class ScheduleMarkdownService {
|
|
|
22448
22688
|
* @param backtest - Whether running in backtest mode
|
|
22449
22689
|
* @returns Unique string key for memoization
|
|
22450
22690
|
*/
|
|
22451
|
-
const CREATE_KEY_FN$
|
|
22691
|
+
const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
22452
22692
|
const parts = [symbol, strategyName, exchangeName];
|
|
22453
22693
|
if (frameName)
|
|
22454
22694
|
parts.push(frameName);
|
|
@@ -22693,7 +22933,7 @@ class PerformanceMarkdownService {
|
|
|
22693
22933
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22694
22934
|
* Each combination gets its own isolated storage instance.
|
|
22695
22935
|
*/
|
|
22696
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22936
|
+
this.getStorage = functoolsKit.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));
|
|
22697
22937
|
/**
|
|
22698
22938
|
* Subscribes to performance emitter to receive performance events.
|
|
22699
22939
|
* Protected against multiple subscriptions.
|
|
@@ -22860,7 +23100,7 @@ class PerformanceMarkdownService {
|
|
|
22860
23100
|
payload,
|
|
22861
23101
|
});
|
|
22862
23102
|
if (payload) {
|
|
22863
|
-
const key = CREATE_KEY_FN$
|
|
23103
|
+
const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22864
23104
|
this.getStorage.clear(key);
|
|
22865
23105
|
}
|
|
22866
23106
|
else {
|
|
@@ -23339,7 +23579,7 @@ class WalkerMarkdownService {
|
|
|
23339
23579
|
* @param backtest - Whether running in backtest mode
|
|
23340
23580
|
* @returns Unique string key for memoization
|
|
23341
23581
|
*/
|
|
23342
|
-
const CREATE_KEY_FN$
|
|
23582
|
+
const CREATE_KEY_FN$j = (exchangeName, frameName, backtest) => {
|
|
23343
23583
|
const parts = [exchangeName];
|
|
23344
23584
|
if (frameName)
|
|
23345
23585
|
parts.push(frameName);
|
|
@@ -23786,7 +24026,7 @@ class HeatMarkdownService {
|
|
|
23786
24026
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
23787
24027
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
23788
24028
|
*/
|
|
23789
|
-
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24029
|
+
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
23790
24030
|
/**
|
|
23791
24031
|
* Subscribes to signal emitter to receive tick events.
|
|
23792
24032
|
* Protected against multiple subscriptions.
|
|
@@ -24004,7 +24244,7 @@ class HeatMarkdownService {
|
|
|
24004
24244
|
payload,
|
|
24005
24245
|
});
|
|
24006
24246
|
if (payload) {
|
|
24007
|
-
const key = CREATE_KEY_FN$
|
|
24247
|
+
const key = CREATE_KEY_FN$j(payload.exchangeName, payload.frameName, payload.backtest);
|
|
24008
24248
|
this.getStorage.clear(key);
|
|
24009
24249
|
}
|
|
24010
24250
|
else {
|
|
@@ -25035,7 +25275,7 @@ class ClientPartial {
|
|
|
25035
25275
|
* @param backtest - Whether running in backtest mode
|
|
25036
25276
|
* @returns Unique string key for memoization
|
|
25037
25277
|
*/
|
|
25038
|
-
const CREATE_KEY_FN$
|
|
25278
|
+
const CREATE_KEY_FN$i = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
25039
25279
|
/**
|
|
25040
25280
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
25041
25281
|
*
|
|
@@ -25157,7 +25397,7 @@ class PartialConnectionService {
|
|
|
25157
25397
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
25158
25398
|
* Value: ClientPartial instance with logger and event emitters
|
|
25159
25399
|
*/
|
|
25160
|
-
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
25400
|
+
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$i(signalId, backtest), (signalId, backtest) => {
|
|
25161
25401
|
return new ClientPartial({
|
|
25162
25402
|
signalId,
|
|
25163
25403
|
logger: this.loggerService,
|
|
@@ -25247,7 +25487,7 @@ class PartialConnectionService {
|
|
|
25247
25487
|
const partial = this.getPartial(data.id, backtest);
|
|
25248
25488
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
25249
25489
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
25250
|
-
const key = CREATE_KEY_FN$
|
|
25490
|
+
const key = CREATE_KEY_FN$i(data.id, backtest);
|
|
25251
25491
|
this.getPartial.clear(key);
|
|
25252
25492
|
};
|
|
25253
25493
|
}
|
|
@@ -25263,7 +25503,7 @@ class PartialConnectionService {
|
|
|
25263
25503
|
* @param backtest - Whether running in backtest mode
|
|
25264
25504
|
* @returns Unique string key for memoization
|
|
25265
25505
|
*/
|
|
25266
|
-
const CREATE_KEY_FN$
|
|
25506
|
+
const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
25267
25507
|
const parts = [symbol, strategyName, exchangeName];
|
|
25268
25508
|
if (frameName)
|
|
25269
25509
|
parts.push(frameName);
|
|
@@ -25486,7 +25726,7 @@ class PartialMarkdownService {
|
|
|
25486
25726
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
25487
25727
|
* Each combination gets its own isolated storage instance.
|
|
25488
25728
|
*/
|
|
25489
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25729
|
+
this.getStorage = functoolsKit.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));
|
|
25490
25730
|
/**
|
|
25491
25731
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
25492
25732
|
* Protected against multiple subscriptions.
|
|
@@ -25696,7 +25936,7 @@ class PartialMarkdownService {
|
|
|
25696
25936
|
payload,
|
|
25697
25937
|
});
|
|
25698
25938
|
if (payload) {
|
|
25699
|
-
const key = CREATE_KEY_FN$
|
|
25939
|
+
const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25700
25940
|
this.getStorage.clear(key);
|
|
25701
25941
|
}
|
|
25702
25942
|
else {
|
|
@@ -25712,7 +25952,7 @@ class PartialMarkdownService {
|
|
|
25712
25952
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
25713
25953
|
* @returns Unique string key for memoization
|
|
25714
25954
|
*/
|
|
25715
|
-
const CREATE_KEY_FN$
|
|
25955
|
+
const CREATE_KEY_FN$g = (context) => {
|
|
25716
25956
|
const parts = [context.strategyName, context.exchangeName];
|
|
25717
25957
|
if (context.frameName)
|
|
25718
25958
|
parts.push(context.frameName);
|
|
@@ -25786,7 +26026,7 @@ class PartialGlobalService {
|
|
|
25786
26026
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
25787
26027
|
* @param methodName - Name of the calling method for error tracking
|
|
25788
26028
|
*/
|
|
25789
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
26029
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), (context, methodName) => {
|
|
25790
26030
|
this.loggerService.log("partialGlobalService validate", {
|
|
25791
26031
|
context,
|
|
25792
26032
|
methodName,
|
|
@@ -26241,7 +26481,7 @@ class ClientBreakeven {
|
|
|
26241
26481
|
* @param backtest - Whether running in backtest mode
|
|
26242
26482
|
* @returns Unique string key for memoization
|
|
26243
26483
|
*/
|
|
26244
|
-
const CREATE_KEY_FN$
|
|
26484
|
+
const CREATE_KEY_FN$f = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
26245
26485
|
/**
|
|
26246
26486
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
26247
26487
|
*
|
|
@@ -26327,7 +26567,7 @@ class BreakevenConnectionService {
|
|
|
26327
26567
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
26328
26568
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
26329
26569
|
*/
|
|
26330
|
-
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
26570
|
+
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$f(signalId, backtest), (signalId, backtest) => {
|
|
26331
26571
|
return new ClientBreakeven({
|
|
26332
26572
|
signalId,
|
|
26333
26573
|
logger: this.loggerService,
|
|
@@ -26388,7 +26628,7 @@ class BreakevenConnectionService {
|
|
|
26388
26628
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
26389
26629
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
26390
26630
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
26391
|
-
const key = CREATE_KEY_FN$
|
|
26631
|
+
const key = CREATE_KEY_FN$f(data.id, backtest);
|
|
26392
26632
|
this.getBreakeven.clear(key);
|
|
26393
26633
|
};
|
|
26394
26634
|
}
|
|
@@ -26404,7 +26644,7 @@ class BreakevenConnectionService {
|
|
|
26404
26644
|
* @param backtest - Whether running in backtest mode
|
|
26405
26645
|
* @returns Unique string key for memoization
|
|
26406
26646
|
*/
|
|
26407
|
-
const CREATE_KEY_FN$
|
|
26647
|
+
const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
26408
26648
|
const parts = [symbol, strategyName, exchangeName];
|
|
26409
26649
|
if (frameName)
|
|
26410
26650
|
parts.push(frameName);
|
|
@@ -26579,7 +26819,7 @@ class BreakevenMarkdownService {
|
|
|
26579
26819
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26580
26820
|
* Each combination gets its own isolated storage instance.
|
|
26581
26821
|
*/
|
|
26582
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26822
|
+
this.getStorage = functoolsKit.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));
|
|
26583
26823
|
/**
|
|
26584
26824
|
* Subscribes to breakeven signal emitter to receive events.
|
|
26585
26825
|
* Protected against multiple subscriptions.
|
|
@@ -26768,7 +27008,7 @@ class BreakevenMarkdownService {
|
|
|
26768
27008
|
payload,
|
|
26769
27009
|
});
|
|
26770
27010
|
if (payload) {
|
|
26771
|
-
const key = CREATE_KEY_FN$
|
|
27011
|
+
const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26772
27012
|
this.getStorage.clear(key);
|
|
26773
27013
|
}
|
|
26774
27014
|
else {
|
|
@@ -26784,7 +27024,7 @@ class BreakevenMarkdownService {
|
|
|
26784
27024
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
26785
27025
|
* @returns Unique string key for memoization
|
|
26786
27026
|
*/
|
|
26787
|
-
const CREATE_KEY_FN$
|
|
27027
|
+
const CREATE_KEY_FN$d = (context) => {
|
|
26788
27028
|
const parts = [context.strategyName, context.exchangeName];
|
|
26789
27029
|
if (context.frameName)
|
|
26790
27030
|
parts.push(context.frameName);
|
|
@@ -26858,7 +27098,7 @@ class BreakevenGlobalService {
|
|
|
26858
27098
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
26859
27099
|
* @param methodName - Name of the calling method for error tracking
|
|
26860
27100
|
*/
|
|
26861
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
27101
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$d(context), (context, methodName) => {
|
|
26862
27102
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
26863
27103
|
context,
|
|
26864
27104
|
methodName,
|
|
@@ -27079,7 +27319,7 @@ class ConfigValidationService {
|
|
|
27079
27319
|
* @param backtest - Whether running in backtest mode
|
|
27080
27320
|
* @returns Unique string key for memoization
|
|
27081
27321
|
*/
|
|
27082
|
-
const CREATE_KEY_FN$
|
|
27322
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
27083
27323
|
const parts = [symbol, strategyName, exchangeName];
|
|
27084
27324
|
if (frameName)
|
|
27085
27325
|
parts.push(frameName);
|
|
@@ -27246,7 +27486,7 @@ class RiskMarkdownService {
|
|
|
27246
27486
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
27247
27487
|
* Each combination gets its own isolated storage instance.
|
|
27248
27488
|
*/
|
|
27249
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
27489
|
+
this.getStorage = functoolsKit.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));
|
|
27250
27490
|
/**
|
|
27251
27491
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
27252
27492
|
* Protected against multiple subscriptions.
|
|
@@ -27435,7 +27675,7 @@ class RiskMarkdownService {
|
|
|
27435
27675
|
payload,
|
|
27436
27676
|
});
|
|
27437
27677
|
if (payload) {
|
|
27438
|
-
const key = CREATE_KEY_FN$
|
|
27678
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
27439
27679
|
this.getStorage.clear(key);
|
|
27440
27680
|
}
|
|
27441
27681
|
else {
|
|
@@ -29074,7 +29314,7 @@ class StrategyReportService {
|
|
|
29074
29314
|
/**
|
|
29075
29315
|
* Logs a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29076
29316
|
*/
|
|
29077
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId) => {
|
|
29317
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId, note) => {
|
|
29078
29318
|
this.loggerService.log("strategyReportService cancelScheduled", {
|
|
29079
29319
|
symbol,
|
|
29080
29320
|
isBacktest,
|
|
@@ -29087,6 +29327,7 @@ class StrategyReportService {
|
|
|
29087
29327
|
await ReportWriter.writeData("strategy", {
|
|
29088
29328
|
action: "cancel-scheduled",
|
|
29089
29329
|
cancelId,
|
|
29330
|
+
note,
|
|
29090
29331
|
symbol,
|
|
29091
29332
|
timestamp,
|
|
29092
29333
|
createdAt,
|
|
@@ -29108,7 +29349,7 @@ class StrategyReportService {
|
|
|
29108
29349
|
/**
|
|
29109
29350
|
* Logs a close-pending event when a pending signal is closed.
|
|
29110
29351
|
*/
|
|
29111
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId) => {
|
|
29352
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId, note) => {
|
|
29112
29353
|
this.loggerService.log("strategyReportService closePending", {
|
|
29113
29354
|
symbol,
|
|
29114
29355
|
isBacktest,
|
|
@@ -29121,6 +29362,7 @@ class StrategyReportService {
|
|
|
29121
29362
|
await ReportWriter.writeData("strategy", {
|
|
29122
29363
|
action: "close-pending",
|
|
29123
29364
|
closeId,
|
|
29365
|
+
note,
|
|
29124
29366
|
symbol,
|
|
29125
29367
|
timestamp,
|
|
29126
29368
|
createdAt,
|
|
@@ -29370,7 +29612,7 @@ class StrategyReportService {
|
|
|
29370
29612
|
/**
|
|
29371
29613
|
* Logs an activate-scheduled event when a scheduled signal is activated early.
|
|
29372
29614
|
*/
|
|
29373
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
29615
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
29374
29616
|
this.loggerService.log("strategyReportService activateScheduled", {
|
|
29375
29617
|
symbol,
|
|
29376
29618
|
currentPrice,
|
|
@@ -29384,6 +29626,7 @@ class StrategyReportService {
|
|
|
29384
29626
|
await ReportWriter.writeData("strategy", {
|
|
29385
29627
|
action: "activate-scheduled",
|
|
29386
29628
|
activateId,
|
|
29629
|
+
note,
|
|
29387
29630
|
currentPrice,
|
|
29388
29631
|
symbol,
|
|
29389
29632
|
timestamp,
|
|
@@ -29477,14 +29720,14 @@ class StrategyReportService {
|
|
|
29477
29720
|
exchangeName: event.exchangeName,
|
|
29478
29721
|
frameName: event.frameName,
|
|
29479
29722
|
strategyName: event.strategyName,
|
|
29480
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId));
|
|
29723
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId, event.note));
|
|
29481
29724
|
const unClosePending = strategyCommitSubject
|
|
29482
29725
|
.filter(({ action }) => action === "close-pending")
|
|
29483
29726
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
29484
29727
|
exchangeName: event.exchangeName,
|
|
29485
29728
|
frameName: event.frameName,
|
|
29486
29729
|
strategyName: event.strategyName,
|
|
29487
|
-
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId));
|
|
29730
|
+
}, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId, event.note));
|
|
29488
29731
|
const unPartialProfit = strategyCommitSubject
|
|
29489
29732
|
.filter(({ action }) => action === "partial-profit")
|
|
29490
29733
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -29526,7 +29769,7 @@ class StrategyReportService {
|
|
|
29526
29769
|
exchangeName: event.exchangeName,
|
|
29527
29770
|
frameName: event.frameName,
|
|
29528
29771
|
strategyName: event.strategyName,
|
|
29529
|
-
}, 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));
|
|
29772
|
+
}, 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));
|
|
29530
29773
|
const unAverageBuy = strategyCommitSubject
|
|
29531
29774
|
.filter(({ action }) => action === "average-buy")
|
|
29532
29775
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -29814,7 +30057,7 @@ class HighestProfitReportService {
|
|
|
29814
30057
|
* @returns Colon-separated key string for memoization
|
|
29815
30058
|
* @internal
|
|
29816
30059
|
*/
|
|
29817
|
-
const CREATE_KEY_FN$
|
|
30060
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29818
30061
|
const parts = [symbol, strategyName, exchangeName];
|
|
29819
30062
|
if (frameName)
|
|
29820
30063
|
parts.push(frameName);
|
|
@@ -30056,7 +30299,7 @@ class StrategyMarkdownService {
|
|
|
30056
30299
|
*
|
|
30057
30300
|
* @internal
|
|
30058
30301
|
*/
|
|
30059
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30302
|
+
this.getStorage = functoolsKit.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));
|
|
30060
30303
|
/**
|
|
30061
30304
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
30062
30305
|
*
|
|
@@ -30065,8 +30308,9 @@ class StrategyMarkdownService {
|
|
|
30065
30308
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30066
30309
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30067
30310
|
* @param cancelId - Optional identifier for the cancellation reason
|
|
30311
|
+
* @param note - Optional note from commit payload
|
|
30068
30312
|
*/
|
|
30069
|
-
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId) => {
|
|
30313
|
+
this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId, note) => {
|
|
30070
30314
|
this.loggerService.log("strategyMarkdownService cancelScheduled", {
|
|
30071
30315
|
symbol,
|
|
30072
30316
|
isBacktest,
|
|
@@ -30087,6 +30331,7 @@ class StrategyMarkdownService {
|
|
|
30087
30331
|
action: "cancel-scheduled",
|
|
30088
30332
|
pnl,
|
|
30089
30333
|
cancelId,
|
|
30334
|
+
note,
|
|
30090
30335
|
createdAt,
|
|
30091
30336
|
backtest: isBacktest,
|
|
30092
30337
|
});
|
|
@@ -30099,8 +30344,9 @@ class StrategyMarkdownService {
|
|
|
30099
30344
|
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
30100
30345
|
* @param timestamp - Timestamp from StrategyCommitContract (execution context time)
|
|
30101
30346
|
* @param closeId - Optional identifier for the close reason
|
|
30347
|
+
* @param note - Optional note from commit payload
|
|
30102
30348
|
*/
|
|
30103
|
-
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId) => {
|
|
30349
|
+
this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId, note) => {
|
|
30104
30350
|
this.loggerService.log("strategyMarkdownService closePending", {
|
|
30105
30351
|
symbol,
|
|
30106
30352
|
isBacktest,
|
|
@@ -30121,6 +30367,7 @@ class StrategyMarkdownService {
|
|
|
30121
30367
|
action: "close-pending",
|
|
30122
30368
|
pnl,
|
|
30123
30369
|
closeId,
|
|
30370
|
+
note,
|
|
30124
30371
|
createdAt,
|
|
30125
30372
|
backtest: isBacktest,
|
|
30126
30373
|
});
|
|
@@ -30419,8 +30666,9 @@ class StrategyMarkdownService {
|
|
|
30419
30666
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
30420
30667
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
30421
30668
|
* @param activateId - Optional identifier for the activation reason
|
|
30669
|
+
* @param note - Optional note from commit payload
|
|
30422
30670
|
*/
|
|
30423
|
-
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
|
|
30671
|
+
this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
|
|
30424
30672
|
this.loggerService.log("strategyMarkdownService activateScheduled", {
|
|
30425
30673
|
symbol,
|
|
30426
30674
|
currentPrice,
|
|
@@ -30443,6 +30691,7 @@ class StrategyMarkdownService {
|
|
|
30443
30691
|
pnl,
|
|
30444
30692
|
totalPartials,
|
|
30445
30693
|
activateId,
|
|
30694
|
+
note,
|
|
30446
30695
|
currentPrice,
|
|
30447
30696
|
createdAt,
|
|
30448
30697
|
backtest: isBacktest,
|
|
@@ -30624,7 +30873,7 @@ class StrategyMarkdownService {
|
|
|
30624
30873
|
this.clear = async (payload) => {
|
|
30625
30874
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
30626
30875
|
if (payload) {
|
|
30627
|
-
const key = CREATE_KEY_FN$
|
|
30876
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30628
30877
|
this.getStorage.clear(key);
|
|
30629
30878
|
}
|
|
30630
30879
|
else {
|
|
@@ -30647,14 +30896,14 @@ class StrategyMarkdownService {
|
|
|
30647
30896
|
exchangeName: event.exchangeName,
|
|
30648
30897
|
frameName: event.frameName,
|
|
30649
30898
|
strategyName: event.strategyName,
|
|
30650
|
-
}, event.timestamp, event.signalId, event.pnl, event.cancelId));
|
|
30899
|
+
}, event.timestamp, event.signalId, event.pnl, event.cancelId, event.note));
|
|
30651
30900
|
const unClosePending = strategyCommitSubject
|
|
30652
30901
|
.filter(({ action }) => action === "close-pending")
|
|
30653
30902
|
.connect(async (event) => await this.closePending(event.symbol, event.backtest, {
|
|
30654
30903
|
exchangeName: event.exchangeName,
|
|
30655
30904
|
frameName: event.frameName,
|
|
30656
30905
|
strategyName: event.strategyName,
|
|
30657
|
-
}, event.timestamp, event.signalId, event.pnl, event.closeId));
|
|
30906
|
+
}, event.timestamp, event.signalId, event.pnl, event.closeId, event.note));
|
|
30658
30907
|
const unPartialProfit = strategyCommitSubject
|
|
30659
30908
|
.filter(({ action }) => action === "partial-profit")
|
|
30660
30909
|
.connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
@@ -30696,7 +30945,7 @@ class StrategyMarkdownService {
|
|
|
30696
30945
|
exchangeName: event.exchangeName,
|
|
30697
30946
|
frameName: event.frameName,
|
|
30698
30947
|
strategyName: event.strategyName,
|
|
30699
|
-
}, 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));
|
|
30948
|
+
}, 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));
|
|
30700
30949
|
const unAverageBuy = strategyCommitSubject
|
|
30701
30950
|
.filter(({ action }) => action === "average-buy")
|
|
30702
30951
|
.connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
|
|
@@ -30732,7 +30981,7 @@ class StrategyMarkdownService {
|
|
|
30732
30981
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
30733
30982
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
30734
30983
|
*/
|
|
30735
|
-
const CREATE_KEY_FN$
|
|
30984
|
+
const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30736
30985
|
const parts = [symbol, strategyName, exchangeName];
|
|
30737
30986
|
if (frameName)
|
|
30738
30987
|
parts.push(frameName);
|
|
@@ -30925,7 +31174,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
30925
31174
|
class SyncMarkdownService {
|
|
30926
31175
|
constructor() {
|
|
30927
31176
|
this.loggerService = inject(TYPES.loggerService);
|
|
30928
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31177
|
+
this.getStorage = functoolsKit.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));
|
|
30929
31178
|
/**
|
|
30930
31179
|
* Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
|
|
30931
31180
|
* Protected against multiple subscriptions via `singleshot` — subsequent calls
|
|
@@ -31121,7 +31370,7 @@ class SyncMarkdownService {
|
|
|
31121
31370
|
this.clear = async (payload) => {
|
|
31122
31371
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
31123
31372
|
if (payload) {
|
|
31124
|
-
const key = CREATE_KEY_FN$
|
|
31373
|
+
const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31125
31374
|
this.getStorage.clear(key);
|
|
31126
31375
|
}
|
|
31127
31376
|
else {
|
|
@@ -31134,7 +31383,7 @@ class SyncMarkdownService {
|
|
|
31134
31383
|
/**
|
|
31135
31384
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31136
31385
|
*/
|
|
31137
|
-
const CREATE_KEY_FN$
|
|
31386
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31138
31387
|
const parts = [symbol, strategyName, exchangeName];
|
|
31139
31388
|
if (frameName)
|
|
31140
31389
|
parts.push(frameName);
|
|
@@ -31310,7 +31559,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
31310
31559
|
class HighestProfitMarkdownService {
|
|
31311
31560
|
constructor() {
|
|
31312
31561
|
this.loggerService = inject(TYPES.loggerService);
|
|
31313
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31562
|
+
this.getStorage = functoolsKit.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));
|
|
31314
31563
|
/**
|
|
31315
31564
|
* Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
|
|
31316
31565
|
* events. Protected against multiple subscriptions via `singleshot` — subsequent
|
|
@@ -31476,7 +31725,7 @@ class HighestProfitMarkdownService {
|
|
|
31476
31725
|
this.clear = async (payload) => {
|
|
31477
31726
|
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
31478
31727
|
if (payload) {
|
|
31479
|
-
const key = CREATE_KEY_FN$
|
|
31728
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31480
31729
|
this.getStorage.clear(key);
|
|
31481
31730
|
}
|
|
31482
31731
|
else {
|
|
@@ -31498,7 +31747,7 @@ const LISTEN_TIMEOUT$1 = 120000;
|
|
|
31498
31747
|
* @param backtest - Whether running in backtest mode
|
|
31499
31748
|
* @returns Unique string key for memoization
|
|
31500
31749
|
*/
|
|
31501
|
-
const CREATE_KEY_FN$
|
|
31750
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31502
31751
|
const parts = [symbol, strategyName, exchangeName];
|
|
31503
31752
|
if (frameName)
|
|
31504
31753
|
parts.push(frameName);
|
|
@@ -31541,7 +31790,7 @@ class PriceMetaService {
|
|
|
31541
31790
|
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
31542
31791
|
* Instances are cached until clear() is called.
|
|
31543
31792
|
*/
|
|
31544
|
-
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31793
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
31545
31794
|
/**
|
|
31546
31795
|
* Returns the current market price for the given symbol and context.
|
|
31547
31796
|
*
|
|
@@ -31570,10 +31819,10 @@ class PriceMetaService {
|
|
|
31570
31819
|
if (source.data) {
|
|
31571
31820
|
return source.data;
|
|
31572
31821
|
}
|
|
31573
|
-
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$
|
|
31822
|
+
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...`);
|
|
31574
31823
|
const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
31575
31824
|
if (typeof currentPrice === "symbol") {
|
|
31576
|
-
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$
|
|
31825
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31577
31826
|
}
|
|
31578
31827
|
return currentPrice;
|
|
31579
31828
|
};
|
|
@@ -31615,7 +31864,7 @@ class PriceMetaService {
|
|
|
31615
31864
|
this.getSource.clear();
|
|
31616
31865
|
return;
|
|
31617
31866
|
}
|
|
31618
|
-
const key = CREATE_KEY_FN$
|
|
31867
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31619
31868
|
this.getSource.clear(key);
|
|
31620
31869
|
};
|
|
31621
31870
|
}
|
|
@@ -31633,7 +31882,7 @@ const LISTEN_TIMEOUT = 120000;
|
|
|
31633
31882
|
* @param backtest - Whether running in backtest mode
|
|
31634
31883
|
* @returns Unique string key for memoization
|
|
31635
31884
|
*/
|
|
31636
|
-
const CREATE_KEY_FN$
|
|
31885
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31637
31886
|
const parts = [symbol, strategyName, exchangeName];
|
|
31638
31887
|
if (frameName)
|
|
31639
31888
|
parts.push(frameName);
|
|
@@ -31676,7 +31925,7 @@ class TimeMetaService {
|
|
|
31676
31925
|
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
31677
31926
|
* Instances are cached until clear() is called.
|
|
31678
31927
|
*/
|
|
31679
|
-
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31928
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
31680
31929
|
/**
|
|
31681
31930
|
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
31682
31931
|
*
|
|
@@ -31704,10 +31953,10 @@ class TimeMetaService {
|
|
|
31704
31953
|
if (source.data) {
|
|
31705
31954
|
return source.data;
|
|
31706
31955
|
}
|
|
31707
|
-
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$
|
|
31956
|
+
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...`);
|
|
31708
31957
|
const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
31709
31958
|
if (typeof timestamp === "symbol") {
|
|
31710
|
-
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$
|
|
31959
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31711
31960
|
}
|
|
31712
31961
|
return timestamp;
|
|
31713
31962
|
};
|
|
@@ -31749,7 +31998,7 @@ class TimeMetaService {
|
|
|
31749
31998
|
this.getSource.clear();
|
|
31750
31999
|
return;
|
|
31751
32000
|
}
|
|
31752
|
-
const key = CREATE_KEY_FN$
|
|
32001
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31753
32002
|
this.getSource.clear(key);
|
|
31754
32003
|
};
|
|
31755
32004
|
}
|
|
@@ -31845,7 +32094,7 @@ class MaxDrawdownReportService {
|
|
|
31845
32094
|
/**
|
|
31846
32095
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31847
32096
|
*/
|
|
31848
|
-
const CREATE_KEY_FN$
|
|
32097
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31849
32098
|
const parts = [symbol, strategyName, exchangeName];
|
|
31850
32099
|
if (frameName)
|
|
31851
32100
|
parts.push(frameName);
|
|
@@ -31969,7 +32218,7 @@ class ReportStorage {
|
|
|
31969
32218
|
class MaxDrawdownMarkdownService {
|
|
31970
32219
|
constructor() {
|
|
31971
32220
|
this.loggerService = inject(TYPES.loggerService);
|
|
31972
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
32221
|
+
this.getStorage = functoolsKit.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));
|
|
31973
32222
|
/**
|
|
31974
32223
|
* Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
|
|
31975
32224
|
* events. Protected against multiple subscriptions via `singleshot`.
|
|
@@ -32048,7 +32297,7 @@ class MaxDrawdownMarkdownService {
|
|
|
32048
32297
|
this.clear = async (payload) => {
|
|
32049
32298
|
this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
|
|
32050
32299
|
if (payload) {
|
|
32051
|
-
const key = CREATE_KEY_FN$
|
|
32300
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
32052
32301
|
this.getStorage.clear(key);
|
|
32053
32302
|
}
|
|
32054
32303
|
else {
|
|
@@ -35181,6 +35430,8 @@ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strateg
|
|
|
35181
35430
|
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35182
35431
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35183
35432
|
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
35433
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlPercentage";
|
|
35434
|
+
const GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlCost";
|
|
35184
35435
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
35185
35436
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
35186
35437
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -35196,7 +35447,7 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35196
35447
|
*
|
|
35197
35448
|
* @param symbol - Trading pair symbol
|
|
35198
35449
|
* @param strategyName - Strategy name
|
|
35199
|
-
* @param
|
|
35450
|
+
* @param payload - Optional commit payload with id and note
|
|
35200
35451
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
35201
35452
|
*
|
|
35202
35453
|
* @example
|
|
@@ -35204,13 +35455,13 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
|
|
|
35204
35455
|
* import { commitCancelScheduled } from "backtest-kit";
|
|
35205
35456
|
*
|
|
35206
35457
|
* // Cancel scheduled signal with custom ID
|
|
35207
|
-
* await commitCancelScheduled("BTCUSDT", "manual-cancel-001");
|
|
35458
|
+
* await commitCancelScheduled("BTCUSDT", { id: "manual-cancel-001" });
|
|
35208
35459
|
* ```
|
|
35209
35460
|
*/
|
|
35210
|
-
async function commitCancelScheduled(symbol,
|
|
35461
|
+
async function commitCancelScheduled(symbol, payload = {}) {
|
|
35211
35462
|
backtest.loggerService.info(CANCEL_SCHEDULED_METHOD_NAME, {
|
|
35212
35463
|
symbol,
|
|
35213
|
-
|
|
35464
|
+
payload,
|
|
35214
35465
|
});
|
|
35215
35466
|
if (!ExecutionContextService.hasContext()) {
|
|
35216
35467
|
throw new Error("commitCancelScheduled requires an execution context");
|
|
@@ -35220,7 +35471,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35220
35471
|
}
|
|
35221
35472
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35222
35473
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35223
|
-
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35474
|
+
await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35224
35475
|
}
|
|
35225
35476
|
/**
|
|
35226
35477
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -35232,7 +35483,7 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35232
35483
|
* Automatically detects backtest/live mode from execution context.
|
|
35233
35484
|
*
|
|
35234
35485
|
* @param symbol - Trading pair symbol
|
|
35235
|
-
* @param
|
|
35486
|
+
* @param payload - Optional commit payload with id and note
|
|
35236
35487
|
* @returns Promise that resolves when pending signal is closed
|
|
35237
35488
|
*
|
|
35238
35489
|
* @example
|
|
@@ -35240,13 +35491,13 @@ async function commitCancelScheduled(symbol, cancelId) {
|
|
|
35240
35491
|
* import { commitClosePending } from "backtest-kit";
|
|
35241
35492
|
*
|
|
35242
35493
|
* // Close pending signal with custom ID
|
|
35243
|
-
* await commitClosePending("BTCUSDT", "manual-close-001");
|
|
35494
|
+
* await commitClosePending("BTCUSDT", { id: "manual-close-001" });
|
|
35244
35495
|
* ```
|
|
35245
35496
|
*/
|
|
35246
|
-
async function commitClosePending(symbol,
|
|
35497
|
+
async function commitClosePending(symbol, payload = {}) {
|
|
35247
35498
|
backtest.loggerService.info(CLOSE_PENDING_METHOD_NAME, {
|
|
35248
35499
|
symbol,
|
|
35249
|
-
|
|
35500
|
+
payload,
|
|
35250
35501
|
});
|
|
35251
35502
|
if (!ExecutionContextService.hasContext()) {
|
|
35252
35503
|
throw new Error("commitClosePending requires an execution context");
|
|
@@ -35256,7 +35507,7 @@ async function commitClosePending(symbol, closeId) {
|
|
|
35256
35507
|
}
|
|
35257
35508
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35258
35509
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35259
|
-
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35510
|
+
await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35260
35511
|
}
|
|
35261
35512
|
/**
|
|
35262
35513
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -35719,7 +35970,7 @@ async function commitBreakeven(symbol) {
|
|
|
35719
35970
|
* Automatically detects backtest/live mode from execution context.
|
|
35720
35971
|
*
|
|
35721
35972
|
* @param symbol - Trading pair symbol
|
|
35722
|
-
* @param
|
|
35973
|
+
* @param payload - Optional commit payload with id and note
|
|
35723
35974
|
* @returns Promise that resolves when activation flag is set
|
|
35724
35975
|
*
|
|
35725
35976
|
* @example
|
|
@@ -35727,13 +35978,13 @@ async function commitBreakeven(symbol) {
|
|
|
35727
35978
|
* import { commitActivateScheduled } from "backtest-kit";
|
|
35728
35979
|
*
|
|
35729
35980
|
* // Activate scheduled signal early with custom ID
|
|
35730
|
-
* await commitActivateScheduled("BTCUSDT", "manual-activate-001");
|
|
35981
|
+
* await commitActivateScheduled("BTCUSDT", { id: "manual-activate-001" });
|
|
35731
35982
|
* ```
|
|
35732
35983
|
*/
|
|
35733
|
-
async function commitActivateScheduled(symbol,
|
|
35984
|
+
async function commitActivateScheduled(symbol, payload = {}) {
|
|
35734
35985
|
backtest.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
|
|
35735
35986
|
symbol,
|
|
35736
|
-
|
|
35987
|
+
payload,
|
|
35737
35988
|
});
|
|
35738
35989
|
if (!ExecutionContextService.hasContext()) {
|
|
35739
35990
|
throw new Error("commitActivateScheduled requires an execution context");
|
|
@@ -35743,7 +35994,7 @@ async function commitActivateScheduled(symbol, activateId) {
|
|
|
35743
35994
|
}
|
|
35744
35995
|
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
35745
35996
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
35746
|
-
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName },
|
|
35997
|
+
await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
|
|
35747
35998
|
}
|
|
35748
35999
|
/**
|
|
35749
36000
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -36931,6 +37182,64 @@ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
|
36931
37182
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36932
37183
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36933
37184
|
}
|
|
37185
|
+
/**
|
|
37186
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
37187
|
+
*
|
|
37188
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
37189
|
+
* Returns null if no pending signal exists.
|
|
37190
|
+
*
|
|
37191
|
+
* @param symbol - Trading pair symbol
|
|
37192
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
37193
|
+
*
|
|
37194
|
+
* @example
|
|
37195
|
+
* ```typescript
|
|
37196
|
+
* import { getMaxDrawdownDistancePnlPercentage } from "backtest-kit";
|
|
37197
|
+
*
|
|
37198
|
+
* const dist = await getMaxDrawdownDistancePnlPercentage("BTCUSDT");
|
|
37199
|
+
* // e.g. 3.5 (peak was +3.5% above trough)
|
|
37200
|
+
* ```
|
|
37201
|
+
*/
|
|
37202
|
+
async function getMaxDrawdownDistancePnlPercentage(symbol) {
|
|
37203
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
37204
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37205
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires an execution context");
|
|
37206
|
+
}
|
|
37207
|
+
if (!MethodContextService.hasContext()) {
|
|
37208
|
+
throw new Error("getMaxDrawdownDistancePnlPercentage requires a method context");
|
|
37209
|
+
}
|
|
37210
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37211
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37212
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37213
|
+
}
|
|
37214
|
+
/**
|
|
37215
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
37216
|
+
*
|
|
37217
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
37218
|
+
* Returns null if no pending signal exists.
|
|
37219
|
+
*
|
|
37220
|
+
* @param symbol - Trading pair symbol
|
|
37221
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
37222
|
+
*
|
|
37223
|
+
* @example
|
|
37224
|
+
* ```typescript
|
|
37225
|
+
* import { getMaxDrawdownDistancePnlCost } from "backtest-kit";
|
|
37226
|
+
*
|
|
37227
|
+
* const dist = await getMaxDrawdownDistancePnlCost("BTCUSDT");
|
|
37228
|
+
* // e.g. 7.2 (peak was $7.2 above trough)
|
|
37229
|
+
* ```
|
|
37230
|
+
*/
|
|
37231
|
+
async function getMaxDrawdownDistancePnlCost(symbol) {
|
|
37232
|
+
backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
37233
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37234
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires an execution context");
|
|
37235
|
+
}
|
|
37236
|
+
if (!MethodContextService.hasContext()) {
|
|
37237
|
+
throw new Error("getMaxDrawdownDistancePnlCost requires a method context");
|
|
37238
|
+
}
|
|
37239
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
37240
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
37241
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37242
|
+
}
|
|
36934
37243
|
/**
|
|
36935
37244
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36936
37245
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38604,6 +38913,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE =
|
|
|
38604
38913
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38605
38914
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38606
38915
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38916
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getMaxDrawdownDistancePnlPercentage";
|
|
38917
|
+
const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "BacktestUtils.getMaxDrawdownDistancePnlCost";
|
|
38607
38918
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38608
38919
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38609
38920
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39985,6 +40296,62 @@ class BacktestUtils {
|
|
|
39985
40296
|
}
|
|
39986
40297
|
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39987
40298
|
};
|
|
40299
|
+
/**
|
|
40300
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
40301
|
+
*
|
|
40302
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
40303
|
+
* Returns null if no pending signal exists.
|
|
40304
|
+
*
|
|
40305
|
+
* @param symbol - Trading pair symbol
|
|
40306
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40307
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
40308
|
+
*/
|
|
40309
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
40310
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
40311
|
+
symbol,
|
|
40312
|
+
context,
|
|
40313
|
+
});
|
|
40314
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40315
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40316
|
+
{
|
|
40317
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40318
|
+
riskName &&
|
|
40319
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
40320
|
+
riskList &&
|
|
40321
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40322
|
+
actions &&
|
|
40323
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
40324
|
+
}
|
|
40325
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(true, symbol, context);
|
|
40326
|
+
};
|
|
40327
|
+
/**
|
|
40328
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
40329
|
+
*
|
|
40330
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
40331
|
+
* Returns null if no pending signal exists.
|
|
40332
|
+
*
|
|
40333
|
+
* @param symbol - Trading pair symbol
|
|
40334
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40335
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
40336
|
+
*/
|
|
40337
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
40338
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
40339
|
+
symbol,
|
|
40340
|
+
context,
|
|
40341
|
+
});
|
|
40342
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40343
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40344
|
+
{
|
|
40345
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
40346
|
+
riskName &&
|
|
40347
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
40348
|
+
riskList &&
|
|
40349
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40350
|
+
actions &&
|
|
40351
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
40352
|
+
}
|
|
40353
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(true, symbol, context);
|
|
40354
|
+
};
|
|
39988
40355
|
/**
|
|
39989
40356
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39990
40357
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40119,7 +40486,7 @@ class BacktestUtils {
|
|
|
40119
40486
|
* @param symbol - Trading pair symbol
|
|
40120
40487
|
* @param strategyName - Strategy name
|
|
40121
40488
|
* @param context - Execution context with exchangeName and frameName
|
|
40122
|
-
* @param
|
|
40489
|
+
* @param payload - Optional commit payload with id and note
|
|
40123
40490
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
40124
40491
|
*
|
|
40125
40492
|
* @example
|
|
@@ -40129,14 +40496,14 @@ class BacktestUtils {
|
|
|
40129
40496
|
* exchangeName: "binance",
|
|
40130
40497
|
* frameName: "frame1",
|
|
40131
40498
|
* strategyName: "my-strategy"
|
|
40132
|
-
* }, "manual-cancel-001");
|
|
40499
|
+
* }, { id: "manual-cancel-001" });
|
|
40133
40500
|
* ```
|
|
40134
40501
|
*/
|
|
40135
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
40502
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
40136
40503
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
40137
40504
|
symbol,
|
|
40138
40505
|
context,
|
|
40139
|
-
|
|
40506
|
+
payload,
|
|
40140
40507
|
});
|
|
40141
40508
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
40142
40509
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -40149,7 +40516,7 @@ class BacktestUtils {
|
|
|
40149
40516
|
actions &&
|
|
40150
40517
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED));
|
|
40151
40518
|
}
|
|
40152
|
-
await backtest.strategyCoreService.cancelScheduled(true, symbol, context,
|
|
40519
|
+
await backtest.strategyCoreService.cancelScheduled(true, symbol, context, payload);
|
|
40153
40520
|
};
|
|
40154
40521
|
/**
|
|
40155
40522
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -40160,7 +40527,7 @@ class BacktestUtils {
|
|
|
40160
40527
|
*
|
|
40161
40528
|
* @param symbol - Trading pair symbol
|
|
40162
40529
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40163
|
-
* @param
|
|
40530
|
+
* @param payload - Optional commit payload with id and note
|
|
40164
40531
|
* @returns Promise that resolves when pending signal is closed
|
|
40165
40532
|
*
|
|
40166
40533
|
* @example
|
|
@@ -40170,14 +40537,14 @@ class BacktestUtils {
|
|
|
40170
40537
|
* exchangeName: "binance",
|
|
40171
40538
|
* strategyName: "my-strategy",
|
|
40172
40539
|
* frameName: "1m"
|
|
40173
|
-
* }, "manual-close-001");
|
|
40540
|
+
* }, { id: "manual-close-001" });
|
|
40174
40541
|
* ```
|
|
40175
40542
|
*/
|
|
40176
|
-
this.commitClosePending = async (symbol, context,
|
|
40543
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
40177
40544
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_CLOSE_PENDING, {
|
|
40178
40545
|
symbol,
|
|
40179
40546
|
context,
|
|
40180
|
-
|
|
40547
|
+
payload,
|
|
40181
40548
|
});
|
|
40182
40549
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
40183
40550
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -40190,7 +40557,7 @@ class BacktestUtils {
|
|
|
40190
40557
|
actions &&
|
|
40191
40558
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CLOSE_PENDING));
|
|
40192
40559
|
}
|
|
40193
|
-
await backtest.strategyCoreService.closePending(true, symbol, context,
|
|
40560
|
+
await backtest.strategyCoreService.closePending(true, symbol, context, payload);
|
|
40194
40561
|
};
|
|
40195
40562
|
/**
|
|
40196
40563
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -40826,7 +41193,7 @@ class BacktestUtils {
|
|
|
40826
41193
|
*
|
|
40827
41194
|
* @param symbol - Trading pair symbol
|
|
40828
41195
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40829
|
-
* @param
|
|
41196
|
+
* @param payload - Optional commit payload with id and note
|
|
40830
41197
|
* @returns Promise that resolves when activation flag is set
|
|
40831
41198
|
*
|
|
40832
41199
|
* @example
|
|
@@ -40836,14 +41203,14 @@ class BacktestUtils {
|
|
|
40836
41203
|
* strategyName: "my-strategy",
|
|
40837
41204
|
* exchangeName: "binance",
|
|
40838
41205
|
* frameName: "1h"
|
|
40839
|
-
* }, "manual-activate-001");
|
|
41206
|
+
* }, { id: "manual-activate-001" });
|
|
40840
41207
|
* ```
|
|
40841
41208
|
*/
|
|
40842
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
41209
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
40843
41210
|
backtest.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
40844
41211
|
symbol,
|
|
40845
41212
|
context,
|
|
40846
|
-
|
|
41213
|
+
payload,
|
|
40847
41214
|
});
|
|
40848
41215
|
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
40849
41216
|
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -40856,7 +41223,7 @@ class BacktestUtils {
|
|
|
40856
41223
|
actions &&
|
|
40857
41224
|
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
40858
41225
|
}
|
|
40859
|
-
await backtest.strategyCoreService.activateScheduled(true, symbol, context,
|
|
41226
|
+
await backtest.strategyCoreService.activateScheduled(true, symbol, context, payload);
|
|
40860
41227
|
};
|
|
40861
41228
|
/**
|
|
40862
41229
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -41114,6 +41481,8 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "Li
|
|
|
41114
41481
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41115
41482
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41116
41483
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
41484
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getMaxDrawdownDistancePnlPercentage";
|
|
41485
|
+
const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "LiveUtils.getMaxDrawdownDistancePnlCost";
|
|
41117
41486
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
41118
41487
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
41119
41488
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -42638,6 +43007,70 @@ class LiveUtils {
|
|
|
42638
43007
|
frameName: "",
|
|
42639
43008
|
});
|
|
42640
43009
|
};
|
|
43010
|
+
/**
|
|
43011
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
43012
|
+
*
|
|
43013
|
+
* Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
|
|
43014
|
+
* Returns null if no pending signal exists.
|
|
43015
|
+
*
|
|
43016
|
+
* @param symbol - Trading pair symbol
|
|
43017
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
43018
|
+
* @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
|
|
43019
|
+
*/
|
|
43020
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
|
|
43021
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
|
|
43022
|
+
symbol,
|
|
43023
|
+
context,
|
|
43024
|
+
});
|
|
43025
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
43026
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
43027
|
+
{
|
|
43028
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
43029
|
+
riskName &&
|
|
43030
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
43031
|
+
riskList &&
|
|
43032
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
43033
|
+
actions &&
|
|
43034
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
43035
|
+
}
|
|
43036
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(false, symbol, {
|
|
43037
|
+
strategyName: context.strategyName,
|
|
43038
|
+
exchangeName: context.exchangeName,
|
|
43039
|
+
frameName: "",
|
|
43040
|
+
});
|
|
43041
|
+
};
|
|
43042
|
+
/**
|
|
43043
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
43044
|
+
*
|
|
43045
|
+
* Computed as: max(0, peakPnlCost - fallPnlCost).
|
|
43046
|
+
* Returns null if no pending signal exists.
|
|
43047
|
+
*
|
|
43048
|
+
* @param symbol - Trading pair symbol
|
|
43049
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
43050
|
+
* @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
|
|
43051
|
+
*/
|
|
43052
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
|
|
43053
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
|
|
43054
|
+
symbol,
|
|
43055
|
+
context,
|
|
43056
|
+
});
|
|
43057
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
43058
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
43059
|
+
{
|
|
43060
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
43061
|
+
riskName &&
|
|
43062
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
43063
|
+
riskList &&
|
|
43064
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
43065
|
+
actions &&
|
|
43066
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
43067
|
+
}
|
|
43068
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(false, symbol, {
|
|
43069
|
+
strategyName: context.strategyName,
|
|
43070
|
+
exchangeName: context.exchangeName,
|
|
43071
|
+
frameName: "",
|
|
43072
|
+
});
|
|
43073
|
+
};
|
|
42641
43074
|
/**
|
|
42642
43075
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
42643
43076
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -42779,7 +43212,7 @@ class LiveUtils {
|
|
|
42779
43212
|
* @param symbol - Trading pair symbol
|
|
42780
43213
|
* @param strategyName - Strategy name
|
|
42781
43214
|
* @param context - Execution context with exchangeName and frameName
|
|
42782
|
-
* @param
|
|
43215
|
+
* @param payload - Optional commit payload with id and note
|
|
42783
43216
|
* @returns Promise that resolves when scheduled signal is cancelled
|
|
42784
43217
|
*
|
|
42785
43218
|
* @example
|
|
@@ -42789,14 +43222,14 @@ class LiveUtils {
|
|
|
42789
43222
|
* exchangeName: "binance",
|
|
42790
43223
|
* frameName: "",
|
|
42791
43224
|
* strategyName: "my-strategy"
|
|
42792
|
-
* }, "manual-cancel-001");
|
|
43225
|
+
* }, { id: "manual-cancel-001" });
|
|
42793
43226
|
* ```
|
|
42794
43227
|
*/
|
|
42795
|
-
this.commitCancelScheduled = async (symbol, context,
|
|
43228
|
+
this.commitCancelScheduled = async (symbol, context, payload = {}) => {
|
|
42796
43229
|
backtest.loggerService.info(LIVE_METHOD_NAME_CANCEL_SCHEDULED, {
|
|
42797
43230
|
symbol,
|
|
42798
43231
|
context,
|
|
42799
|
-
|
|
43232
|
+
payload,
|
|
42800
43233
|
});
|
|
42801
43234
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
42802
43235
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
@@ -42813,7 +43246,7 @@ class LiveUtils {
|
|
|
42813
43246
|
strategyName: context.strategyName,
|
|
42814
43247
|
exchangeName: context.exchangeName,
|
|
42815
43248
|
frameName: "",
|
|
42816
|
-
},
|
|
43249
|
+
}, payload);
|
|
42817
43250
|
};
|
|
42818
43251
|
/**
|
|
42819
43252
|
* Closes the pending signal without stopping the strategy.
|
|
@@ -42824,7 +43257,7 @@ class LiveUtils {
|
|
|
42824
43257
|
*
|
|
42825
43258
|
* @param symbol - Trading pair symbol
|
|
42826
43259
|
* @param context - Execution context with strategyName and exchangeName
|
|
42827
|
-
* @param
|
|
43260
|
+
* @param payload - Optional commit payload with id and note
|
|
42828
43261
|
* @returns Promise that resolves when pending signal is closed
|
|
42829
43262
|
*
|
|
42830
43263
|
* @example
|
|
@@ -42833,14 +43266,14 @@ class LiveUtils {
|
|
|
42833
43266
|
* await Live.commitClose("BTCUSDT", {
|
|
42834
43267
|
* exchangeName: "binance",
|
|
42835
43268
|
* strategyName: "my-strategy"
|
|
42836
|
-
* }, "manual-close-001");
|
|
43269
|
+
* }, { id: "manual-close-001" });
|
|
42837
43270
|
* ```
|
|
42838
43271
|
*/
|
|
42839
|
-
this.commitClosePending = async (symbol, context,
|
|
43272
|
+
this.commitClosePending = async (symbol, context, payload = {}) => {
|
|
42840
43273
|
backtest.loggerService.info(LIVE_METHOD_NAME_CLOSE_PENDING, {
|
|
42841
43274
|
symbol,
|
|
42842
43275
|
context,
|
|
42843
|
-
|
|
43276
|
+
payload,
|
|
42844
43277
|
});
|
|
42845
43278
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
42846
43279
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
@@ -42857,7 +43290,7 @@ class LiveUtils {
|
|
|
42857
43290
|
strategyName: context.strategyName,
|
|
42858
43291
|
exchangeName: context.exchangeName,
|
|
42859
43292
|
frameName: "",
|
|
42860
|
-
},
|
|
43293
|
+
}, payload);
|
|
42861
43294
|
};
|
|
42862
43295
|
/**
|
|
42863
43296
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -43651,7 +44084,7 @@ class LiveUtils {
|
|
|
43651
44084
|
*
|
|
43652
44085
|
* @param symbol - Trading pair symbol
|
|
43653
44086
|
* @param context - Execution context with strategyName and exchangeName
|
|
43654
|
-
* @param
|
|
44087
|
+
* @param payload - Optional commit payload with id and note
|
|
43655
44088
|
* @returns Promise that resolves when activation flag is set
|
|
43656
44089
|
*
|
|
43657
44090
|
* @example
|
|
@@ -43660,14 +44093,14 @@ class LiveUtils {
|
|
|
43660
44093
|
* await Live.commitActivateScheduled("BTCUSDT", {
|
|
43661
44094
|
* strategyName: "my-strategy",
|
|
43662
44095
|
* exchangeName: "binance"
|
|
43663
|
-
* }, "manual-activate-001");
|
|
44096
|
+
* }, { id: "manual-activate-001" });
|
|
43664
44097
|
* ```
|
|
43665
44098
|
*/
|
|
43666
|
-
this.commitActivateScheduled = async (symbol, context,
|
|
44099
|
+
this.commitActivateScheduled = async (symbol, context, payload = {}) => {
|
|
43667
44100
|
backtest.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
|
|
43668
44101
|
symbol,
|
|
43669
44102
|
context,
|
|
43670
|
-
|
|
44103
|
+
payload,
|
|
43671
44104
|
});
|
|
43672
44105
|
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
43673
44106
|
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
@@ -43684,7 +44117,7 @@ class LiveUtils {
|
|
|
43684
44117
|
strategyName: context.strategyName,
|
|
43685
44118
|
exchangeName: context.exchangeName,
|
|
43686
44119
|
frameName: "",
|
|
43687
|
-
},
|
|
44120
|
+
}, payload);
|
|
43688
44121
|
};
|
|
43689
44122
|
/**
|
|
43690
44123
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -44930,6 +45363,503 @@ async function listRiskSchema() {
|
|
|
44930
45363
|
return await backtest.riskValidationService.list();
|
|
44931
45364
|
}
|
|
44932
45365
|
|
|
45366
|
+
const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
|
|
45367
|
+
const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
|
|
45368
|
+
const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
|
|
45369
|
+
const RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistLiveUtils.getLatestSignal";
|
|
45370
|
+
const RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryBacktestUtils.handleActivePing";
|
|
45371
|
+
const RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryBacktestUtils.getLatestSignal";
|
|
45372
|
+
const RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryLiveUtils.handleActivePing";
|
|
45373
|
+
const RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryLiveUtils.getLatestSignal";
|
|
45374
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentBacktestAdapter.handleActivePing";
|
|
45375
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentBacktestAdapter.getLatestSignal";
|
|
45376
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentBacktestAdapter.useRecentAdapter";
|
|
45377
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentBacktestAdapter.usePersist";
|
|
45378
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentBacktestAdapter.useMemory";
|
|
45379
|
+
const RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "RecentBacktestAdapter.clear";
|
|
45380
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentLiveAdapter.handleActivePing";
|
|
45381
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentLiveAdapter.getLatestSignal";
|
|
45382
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentLiveAdapter.useRecentAdapter";
|
|
45383
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentLiveAdapter.usePersist";
|
|
45384
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentLiveAdapter.useMemory";
|
|
45385
|
+
const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
|
|
45386
|
+
const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
|
|
45387
|
+
const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
|
|
45388
|
+
const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
|
|
45389
|
+
/**
|
|
45390
|
+
* Builds a composite storage key from context parts.
|
|
45391
|
+
* Includes backtest flag as the last segment to prevent live/backtest collisions.
|
|
45392
|
+
* @param symbol - Trading pair symbol
|
|
45393
|
+
* @param strategyName - Strategy identifier
|
|
45394
|
+
* @param exchangeName - Exchange identifier
|
|
45395
|
+
* @param frameName - Frame identifier
|
|
45396
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45397
|
+
* @returns Composite key string
|
|
45398
|
+
*/
|
|
45399
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
45400
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
45401
|
+
if (frameName)
|
|
45402
|
+
parts.push(frameName);
|
|
45403
|
+
parts.push(backtest ? "backtest" : "live");
|
|
45404
|
+
return parts.join(":");
|
|
45405
|
+
};
|
|
45406
|
+
/**
|
|
45407
|
+
* Persistent storage adapter for backtest recent signals.
|
|
45408
|
+
*
|
|
45409
|
+
* Features:
|
|
45410
|
+
* - Persists the latest active signal per context to disk using PersistRecentAdapter
|
|
45411
|
+
* - Handles active ping events only
|
|
45412
|
+
*
|
|
45413
|
+
* Use this adapter for backtest recent signal persistence across sessions.
|
|
45414
|
+
*/
|
|
45415
|
+
class RecentPersistBacktestUtils {
|
|
45416
|
+
constructor() {
|
|
45417
|
+
/**
|
|
45418
|
+
* Handles active ping event.
|
|
45419
|
+
* Persists the latest signal to disk via PersistRecentAdapter.
|
|
45420
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45421
|
+
*/
|
|
45422
|
+
this.handleActivePing = async (event) => {
|
|
45423
|
+
backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45424
|
+
signalId: event.data.id,
|
|
45425
|
+
});
|
|
45426
|
+
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45427
|
+
};
|
|
45428
|
+
/**
|
|
45429
|
+
* Retrieves the latest persisted signal for the given context.
|
|
45430
|
+
* @param symbol - Trading pair symbol
|
|
45431
|
+
* @param strategyName - Strategy identifier
|
|
45432
|
+
* @param exchangeName - Exchange identifier
|
|
45433
|
+
* @param frameName - Frame identifier
|
|
45434
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45435
|
+
* @returns The latest signal or null if not found
|
|
45436
|
+
*/
|
|
45437
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45438
|
+
backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45439
|
+
symbol,
|
|
45440
|
+
strategyName,
|
|
45441
|
+
exchangeName,
|
|
45442
|
+
frameName,
|
|
45443
|
+
backtest: backtest$1,
|
|
45444
|
+
});
|
|
45445
|
+
return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45446
|
+
};
|
|
45447
|
+
}
|
|
45448
|
+
}
|
|
45449
|
+
/**
|
|
45450
|
+
* In-memory storage adapter for backtest recent signals.
|
|
45451
|
+
*
|
|
45452
|
+
* Features:
|
|
45453
|
+
* - Stores the latest active signal per context key in memory only
|
|
45454
|
+
* - Fast read/write operations
|
|
45455
|
+
* - Data is lost when application restarts
|
|
45456
|
+
*
|
|
45457
|
+
* Use this adapter for testing or when persistence is not required.
|
|
45458
|
+
*/
|
|
45459
|
+
class RecentMemoryBacktestUtils {
|
|
45460
|
+
constructor() {
|
|
45461
|
+
/** Map of composite context keys to the latest signal */
|
|
45462
|
+
this._signals = new Map();
|
|
45463
|
+
/**
|
|
45464
|
+
* Handles active ping event.
|
|
45465
|
+
* Stores the latest signal in memory under the composite context key.
|
|
45466
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45467
|
+
*/
|
|
45468
|
+
this.handleActivePing = async (event) => {
|
|
45469
|
+
backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45470
|
+
signalId: event.data.id,
|
|
45471
|
+
});
|
|
45472
|
+
const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45473
|
+
this._signals.set(key, event.data);
|
|
45474
|
+
};
|
|
45475
|
+
/**
|
|
45476
|
+
* Retrieves the latest in-memory signal for the given context.
|
|
45477
|
+
* @param symbol - Trading pair symbol
|
|
45478
|
+
* @param strategyName - Strategy identifier
|
|
45479
|
+
* @param exchangeName - Exchange identifier
|
|
45480
|
+
* @param frameName - Frame identifier
|
|
45481
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45482
|
+
* @returns The latest signal or null if not found
|
|
45483
|
+
*/
|
|
45484
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45485
|
+
const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45486
|
+
backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
45487
|
+
return this._signals.get(key) ?? null;
|
|
45488
|
+
};
|
|
45489
|
+
}
|
|
45490
|
+
}
|
|
45491
|
+
/**
|
|
45492
|
+
* Persistent storage adapter for live recent signals.
|
|
45493
|
+
*
|
|
45494
|
+
* Features:
|
|
45495
|
+
* - Persists the latest active signal per context to disk using PersistRecentAdapter
|
|
45496
|
+
* - Handles active ping events only
|
|
45497
|
+
*
|
|
45498
|
+
* Use this adapter (default) for live recent signal persistence across sessions.
|
|
45499
|
+
*/
|
|
45500
|
+
class RecentPersistLiveUtils {
|
|
45501
|
+
constructor() {
|
|
45502
|
+
/**
|
|
45503
|
+
* Handles active ping event.
|
|
45504
|
+
* Persists the latest signal to disk via PersistRecentAdapter.
|
|
45505
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45506
|
+
*/
|
|
45507
|
+
this.handleActivePing = async (event) => {
|
|
45508
|
+
backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45509
|
+
signalId: event.data.id,
|
|
45510
|
+
});
|
|
45511
|
+
await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45512
|
+
};
|
|
45513
|
+
/**
|
|
45514
|
+
* Retrieves the latest persisted signal for the given context.
|
|
45515
|
+
* @param symbol - Trading pair symbol
|
|
45516
|
+
* @param strategyName - Strategy identifier
|
|
45517
|
+
* @param exchangeName - Exchange identifier
|
|
45518
|
+
* @param frameName - Frame identifier
|
|
45519
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45520
|
+
* @returns The latest signal or null if not found
|
|
45521
|
+
*/
|
|
45522
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45523
|
+
backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45524
|
+
symbol,
|
|
45525
|
+
strategyName,
|
|
45526
|
+
exchangeName,
|
|
45527
|
+
frameName,
|
|
45528
|
+
backtest: backtest$1,
|
|
45529
|
+
});
|
|
45530
|
+
return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45531
|
+
};
|
|
45532
|
+
}
|
|
45533
|
+
}
|
|
45534
|
+
/**
|
|
45535
|
+
* In-memory storage adapter for live recent signals.
|
|
45536
|
+
*
|
|
45537
|
+
* Features:
|
|
45538
|
+
* - Stores the latest active signal per context key in memory only
|
|
45539
|
+
* - Fast read/write operations
|
|
45540
|
+
* - Data is lost when application restarts
|
|
45541
|
+
*
|
|
45542
|
+
* Use this adapter for testing or when persistence is not required.
|
|
45543
|
+
*/
|
|
45544
|
+
class RecentMemoryLiveUtils {
|
|
45545
|
+
constructor() {
|
|
45546
|
+
/** Map of composite context keys to the latest signal */
|
|
45547
|
+
this._signals = new Map();
|
|
45548
|
+
/**
|
|
45549
|
+
* Handles active ping event.
|
|
45550
|
+
* Stores the latest signal in memory under the composite context key.
|
|
45551
|
+
* @param event - Active ping contract with signal data and backtest flag
|
|
45552
|
+
*/
|
|
45553
|
+
this.handleActivePing = async (event) => {
|
|
45554
|
+
backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45555
|
+
signalId: event.data.id,
|
|
45556
|
+
});
|
|
45557
|
+
const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
|
|
45558
|
+
this._signals.set(key, event.data);
|
|
45559
|
+
};
|
|
45560
|
+
/**
|
|
45561
|
+
* Retrieves the latest in-memory signal for the given context.
|
|
45562
|
+
* @param symbol - Trading pair symbol
|
|
45563
|
+
* @param strategyName - Strategy identifier
|
|
45564
|
+
* @param exchangeName - Exchange identifier
|
|
45565
|
+
* @param frameName - Frame identifier
|
|
45566
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45567
|
+
* @returns The latest signal or null if not found
|
|
45568
|
+
*/
|
|
45569
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45570
|
+
const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45571
|
+
backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
|
|
45572
|
+
return this._signals.get(key) ?? null;
|
|
45573
|
+
};
|
|
45574
|
+
}
|
|
45575
|
+
}
|
|
45576
|
+
/**
|
|
45577
|
+
* Backtest recent signal adapter with pluggable storage backend.
|
|
45578
|
+
*
|
|
45579
|
+
* Features:
|
|
45580
|
+
* - Adapter pattern for swappable storage implementations
|
|
45581
|
+
* - Default adapter: RecentMemoryBacktestUtils (in-memory storage)
|
|
45582
|
+
* - Alternative adapter: RecentPersistBacktestUtils
|
|
45583
|
+
* - Convenience methods: usePersist(), useMemory()
|
|
45584
|
+
*/
|
|
45585
|
+
class RecentBacktestAdapter {
|
|
45586
|
+
constructor() {
|
|
45587
|
+
/** Internal storage utils instance */
|
|
45588
|
+
this._recentBacktestUtils = new RecentMemoryBacktestUtils();
|
|
45589
|
+
/**
|
|
45590
|
+
* Handles active ping event.
|
|
45591
|
+
* Proxies call to the underlying storage adapter.
|
|
45592
|
+
* @param event - Active ping contract with signal data
|
|
45593
|
+
*/
|
|
45594
|
+
this.handleActivePing = async (event) => {
|
|
45595
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45596
|
+
signalId: event.data.id,
|
|
45597
|
+
});
|
|
45598
|
+
return await this._recentBacktestUtils.handleActivePing(event);
|
|
45599
|
+
};
|
|
45600
|
+
/**
|
|
45601
|
+
* Retrieves the latest signal for the given context.
|
|
45602
|
+
* Proxies call to the underlying storage adapter.
|
|
45603
|
+
* @param symbol - Trading pair symbol
|
|
45604
|
+
* @param strategyName - Strategy identifier
|
|
45605
|
+
* @param exchangeName - Exchange identifier
|
|
45606
|
+
* @param frameName - Frame identifier
|
|
45607
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45608
|
+
* @returns The latest signal or null if not found
|
|
45609
|
+
*/
|
|
45610
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45611
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45612
|
+
symbol,
|
|
45613
|
+
strategyName,
|
|
45614
|
+
exchangeName,
|
|
45615
|
+
frameName,
|
|
45616
|
+
backtest: backtest$1,
|
|
45617
|
+
});
|
|
45618
|
+
return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45619
|
+
};
|
|
45620
|
+
/**
|
|
45621
|
+
* Sets the storage adapter constructor.
|
|
45622
|
+
* All future storage operations will use this adapter.
|
|
45623
|
+
* @param Ctor - Constructor for recent adapter
|
|
45624
|
+
*/
|
|
45625
|
+
this.useRecentAdapter = (Ctor) => {
|
|
45626
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
|
|
45627
|
+
this._recentBacktestUtils = Reflect.construct(Ctor, []);
|
|
45628
|
+
};
|
|
45629
|
+
/**
|
|
45630
|
+
* Switches to persistent storage adapter.
|
|
45631
|
+
* Signals will be persisted to disk.
|
|
45632
|
+
*/
|
|
45633
|
+
this.usePersist = () => {
|
|
45634
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
|
|
45635
|
+
this._recentBacktestUtils = new RecentPersistBacktestUtils();
|
|
45636
|
+
};
|
|
45637
|
+
/**
|
|
45638
|
+
* Switches to in-memory storage adapter (default).
|
|
45639
|
+
* Signals will be stored in memory only.
|
|
45640
|
+
*/
|
|
45641
|
+
this.useMemory = () => {
|
|
45642
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY);
|
|
45643
|
+
this._recentBacktestUtils = new RecentMemoryBacktestUtils();
|
|
45644
|
+
};
|
|
45645
|
+
/**
|
|
45646
|
+
* Clears the cached utils instance by resetting to the default in-memory adapter.
|
|
45647
|
+
*/
|
|
45648
|
+
this.clear = () => {
|
|
45649
|
+
backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
|
|
45650
|
+
this._recentBacktestUtils = new RecentMemoryBacktestUtils();
|
|
45651
|
+
};
|
|
45652
|
+
}
|
|
45653
|
+
}
|
|
45654
|
+
/**
|
|
45655
|
+
* Live recent signal adapter with pluggable storage backend.
|
|
45656
|
+
*
|
|
45657
|
+
* Features:
|
|
45658
|
+
* - Adapter pattern for swappable storage implementations
|
|
45659
|
+
* - Default adapter: RecentPersistLiveUtils (persistent storage)
|
|
45660
|
+
* - Alternative adapter: RecentMemoryLiveUtils
|
|
45661
|
+
* - Convenience methods: usePersist(), useMemory()
|
|
45662
|
+
*/
|
|
45663
|
+
class RecentLiveAdapter {
|
|
45664
|
+
constructor() {
|
|
45665
|
+
/** Internal storage utils instance */
|
|
45666
|
+
this._recentLiveUtils = new RecentPersistLiveUtils();
|
|
45667
|
+
/**
|
|
45668
|
+
* Handles active ping event.
|
|
45669
|
+
* Proxies call to the underlying storage adapter.
|
|
45670
|
+
* @param event - Active ping contract with signal data
|
|
45671
|
+
*/
|
|
45672
|
+
this.handleActivePing = async (event) => {
|
|
45673
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
|
|
45674
|
+
signalId: event.data.id,
|
|
45675
|
+
});
|
|
45676
|
+
return await this._recentLiveUtils.handleActivePing(event);
|
|
45677
|
+
};
|
|
45678
|
+
/**
|
|
45679
|
+
* Retrieves the latest signal for the given context.
|
|
45680
|
+
* Proxies call to the underlying storage adapter.
|
|
45681
|
+
* @param symbol - Trading pair symbol
|
|
45682
|
+
* @param strategyName - Strategy identifier
|
|
45683
|
+
* @param exchangeName - Exchange identifier
|
|
45684
|
+
* @param frameName - Frame identifier
|
|
45685
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45686
|
+
* @returns The latest signal or null if not found
|
|
45687
|
+
*/
|
|
45688
|
+
this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
|
|
45689
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45690
|
+
symbol,
|
|
45691
|
+
strategyName,
|
|
45692
|
+
exchangeName,
|
|
45693
|
+
frameName,
|
|
45694
|
+
backtest: backtest$1,
|
|
45695
|
+
});
|
|
45696
|
+
return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
|
|
45697
|
+
};
|
|
45698
|
+
/**
|
|
45699
|
+
* Sets the storage adapter constructor.
|
|
45700
|
+
* All future storage operations will use this adapter.
|
|
45701
|
+
* @param Ctor - Constructor for recent adapter
|
|
45702
|
+
*/
|
|
45703
|
+
this.useRecentAdapter = (Ctor) => {
|
|
45704
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
|
|
45705
|
+
this._recentLiveUtils = Reflect.construct(Ctor, []);
|
|
45706
|
+
};
|
|
45707
|
+
/**
|
|
45708
|
+
* Switches to persistent storage adapter (default).
|
|
45709
|
+
* Signals will be persisted to disk.
|
|
45710
|
+
*/
|
|
45711
|
+
this.usePersist = () => {
|
|
45712
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
|
|
45713
|
+
this._recentLiveUtils = new RecentPersistLiveUtils();
|
|
45714
|
+
};
|
|
45715
|
+
/**
|
|
45716
|
+
* Switches to in-memory storage adapter.
|
|
45717
|
+
* Signals will be stored in memory only.
|
|
45718
|
+
*/
|
|
45719
|
+
this.useMemory = () => {
|
|
45720
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY);
|
|
45721
|
+
this._recentLiveUtils = new RecentMemoryLiveUtils();
|
|
45722
|
+
};
|
|
45723
|
+
/**
|
|
45724
|
+
* Clears the cached utils instance by resetting to the default persistent adapter.
|
|
45725
|
+
*/
|
|
45726
|
+
this.clear = () => {
|
|
45727
|
+
backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR);
|
|
45728
|
+
this._recentLiveUtils = new RecentPersistLiveUtils();
|
|
45729
|
+
};
|
|
45730
|
+
}
|
|
45731
|
+
}
|
|
45732
|
+
/**
|
|
45733
|
+
* Main recent signal adapter that manages both backtest and live recent signal storage.
|
|
45734
|
+
*
|
|
45735
|
+
* Features:
|
|
45736
|
+
* - Subscribes to activePingSubject for automatic storage updates
|
|
45737
|
+
* - Provides unified access to the latest signal for any context
|
|
45738
|
+
* - Singleshot enable pattern prevents duplicate subscriptions
|
|
45739
|
+
* - Cleanup function for proper unsubscription
|
|
45740
|
+
*/
|
|
45741
|
+
class RecentAdapter {
|
|
45742
|
+
constructor() {
|
|
45743
|
+
/**
|
|
45744
|
+
* Enables recent signal storage by subscribing to activePingSubject.
|
|
45745
|
+
* Uses singleshot to ensure one-time subscription.
|
|
45746
|
+
*
|
|
45747
|
+
* @returns Cleanup function that unsubscribes from all emitters
|
|
45748
|
+
*/
|
|
45749
|
+
this.enable = functoolsKit.singleshot(() => {
|
|
45750
|
+
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_ENABLE);
|
|
45751
|
+
let unBacktest;
|
|
45752
|
+
let unLive;
|
|
45753
|
+
{
|
|
45754
|
+
const unBacktestPingActive = activePingSubject
|
|
45755
|
+
.filter(({ backtest }) => backtest)
|
|
45756
|
+
.connect((event) => RecentBacktest.handleActivePing(event));
|
|
45757
|
+
unBacktest = functoolsKit.compose(() => unBacktestPingActive());
|
|
45758
|
+
}
|
|
45759
|
+
{
|
|
45760
|
+
const unLivePingActive = activePingSubject
|
|
45761
|
+
.filter(({ backtest }) => !backtest)
|
|
45762
|
+
.connect((event) => RecentLive.handleActivePing(event));
|
|
45763
|
+
unLive = functoolsKit.compose(() => unLivePingActive());
|
|
45764
|
+
}
|
|
45765
|
+
const unEnable = () => this.enable.clear();
|
|
45766
|
+
return functoolsKit.compose(() => unBacktest(), () => unLive(), () => unEnable());
|
|
45767
|
+
});
|
|
45768
|
+
/**
|
|
45769
|
+
* Disables recent signal storage by unsubscribing from all emitters.
|
|
45770
|
+
* Safe to call multiple times.
|
|
45771
|
+
*/
|
|
45772
|
+
this.disable = () => {
|
|
45773
|
+
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_DISABLE);
|
|
45774
|
+
if (this.enable.hasValue()) {
|
|
45775
|
+
const lastSubscription = this.enable();
|
|
45776
|
+
lastSubscription();
|
|
45777
|
+
}
|
|
45778
|
+
};
|
|
45779
|
+
/**
|
|
45780
|
+
* Retrieves the latest active signal for the given symbol and context.
|
|
45781
|
+
* Searches backtest storage first, then live storage.
|
|
45782
|
+
*
|
|
45783
|
+
* @param symbol - Trading pair symbol
|
|
45784
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
45785
|
+
* @param backtest - Flag indicating if the context is backtest or live
|
|
45786
|
+
* @returns The latest signal or null if not found
|
|
45787
|
+
* @throws Error if RecentAdapter is not enabled
|
|
45788
|
+
*/
|
|
45789
|
+
this.getLatestSignal = async (symbol, context) => {
|
|
45790
|
+
backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
|
|
45791
|
+
symbol,
|
|
45792
|
+
context,
|
|
45793
|
+
});
|
|
45794
|
+
if (!this.enable.hasValue()) {
|
|
45795
|
+
throw new Error("RecentAdapter is not enabled. Call enable() first.");
|
|
45796
|
+
}
|
|
45797
|
+
let result = null;
|
|
45798
|
+
if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
|
|
45799
|
+
return result;
|
|
45800
|
+
}
|
|
45801
|
+
if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
|
|
45802
|
+
return result;
|
|
45803
|
+
}
|
|
45804
|
+
return null;
|
|
45805
|
+
};
|
|
45806
|
+
}
|
|
45807
|
+
}
|
|
45808
|
+
/**
|
|
45809
|
+
* Global singleton instance of RecentAdapter.
|
|
45810
|
+
* Provides unified recent signal management for backtest and live trading.
|
|
45811
|
+
*/
|
|
45812
|
+
const Recent = new RecentAdapter();
|
|
45813
|
+
/**
|
|
45814
|
+
* Global singleton instance of RecentLiveAdapter.
|
|
45815
|
+
* Provides live trading recent signal storage with pluggable backends.
|
|
45816
|
+
*/
|
|
45817
|
+
const RecentLive = new RecentLiveAdapter();
|
|
45818
|
+
/**
|
|
45819
|
+
* Global singleton instance of RecentBacktestAdapter.
|
|
45820
|
+
* Provides backtest recent signal storage with pluggable backends.
|
|
45821
|
+
*/
|
|
45822
|
+
const RecentBacktest = new RecentBacktestAdapter();
|
|
45823
|
+
|
|
45824
|
+
const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
|
|
45825
|
+
/**
|
|
45826
|
+
* Returns the latest signal (pending or closed) for the current strategy context.
|
|
45827
|
+
*
|
|
45828
|
+
* Does not distinguish between active and closed signals — returns whichever
|
|
45829
|
+
* was recorded last. Useful for cooldown logic: e.g. skip opening a new position
|
|
45830
|
+
* for 4 hours after a stop-loss by checking the timestamp of the latest signal
|
|
45831
|
+
* regardless of its outcome.
|
|
45832
|
+
*
|
|
45833
|
+
* Searches backtest storage first, then live storage.
|
|
45834
|
+
* Returns null if no signal exists at all.
|
|
45835
|
+
*
|
|
45836
|
+
* Automatically detects backtest/live mode from execution context.
|
|
45837
|
+
*
|
|
45838
|
+
* @param symbol - Trading pair symbol
|
|
45839
|
+
* @returns Promise resolving to the latest signal or null
|
|
45840
|
+
*
|
|
45841
|
+
* @example
|
|
45842
|
+
* ```typescript
|
|
45843
|
+
* import { getLatestSignal } from "backtest-kit";
|
|
45844
|
+
*
|
|
45845
|
+
* const latest = await getLatestSignal("BTCUSDT");
|
|
45846
|
+
* if (latest && Date.now() - latest.closedAt < 4 * 60 * 60 * 1000) {
|
|
45847
|
+
* return; // cooldown after SL — skip new signal for 4 hours
|
|
45848
|
+
* }
|
|
45849
|
+
* ```
|
|
45850
|
+
*/
|
|
45851
|
+
async function getLatestSignal(symbol) {
|
|
45852
|
+
backtest.loggerService.info(GET_LATEST_SIGNAL_METHOD_NAME, { symbol });
|
|
45853
|
+
if (!ExecutionContextService.hasContext()) {
|
|
45854
|
+
throw new Error("getLatestSignal requires an execution context");
|
|
45855
|
+
}
|
|
45856
|
+
if (!MethodContextService.hasContext()) {
|
|
45857
|
+
throw new Error("getLatestSignal requires a method context");
|
|
45858
|
+
}
|
|
45859
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
45860
|
+
return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
|
|
45861
|
+
}
|
|
45862
|
+
|
|
44933
45863
|
const DEFAULT_BM25_K1 = 1.5;
|
|
44934
45864
|
const DEFAULT_BM25_B = 0.75;
|
|
44935
45865
|
const DEFAULT_BM25_SCORE = 0.5;
|
|
@@ -48180,6 +49110,67 @@ class LogAdapter {
|
|
|
48180
49110
|
*/
|
|
48181
49111
|
const Log = new LogAdapter();
|
|
48182
49112
|
|
|
49113
|
+
const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
|
|
49114
|
+
/** List of all global subjects whose listeners should be snapshotted for session isolation */
|
|
49115
|
+
const SUBJECT_ISOLATION_LIST = [
|
|
49116
|
+
activePingSubject,
|
|
49117
|
+
backtestScheduleOpenSubject,
|
|
49118
|
+
breakevenSubject,
|
|
49119
|
+
doneBacktestSubject,
|
|
49120
|
+
doneLiveSubject,
|
|
49121
|
+
errorEmitter,
|
|
49122
|
+
exitEmitter,
|
|
49123
|
+
highestProfitSubject,
|
|
49124
|
+
maxDrawdownSubject,
|
|
49125
|
+
partialLossSubject,
|
|
49126
|
+
partialProfitSubject,
|
|
49127
|
+
performanceEmitter,
|
|
49128
|
+
progressBacktestEmitter,
|
|
49129
|
+
riskSubject,
|
|
49130
|
+
schedulePingSubject,
|
|
49131
|
+
shutdownEmitter,
|
|
49132
|
+
signalBacktestEmitter,
|
|
49133
|
+
signalEmitter,
|
|
49134
|
+
signalLiveEmitter,
|
|
49135
|
+
strategyCommitSubject,
|
|
49136
|
+
syncSubject,
|
|
49137
|
+
validationSubject,
|
|
49138
|
+
];
|
|
49139
|
+
/**
|
|
49140
|
+
* Creates a snapshot function for a given subject by clearing its internal
|
|
49141
|
+
* events map and returning a restore function that can put the original listeners back.
|
|
49142
|
+
* @param subject The subject to snapshot
|
|
49143
|
+
* @returns A function that restores the subject's original listeners when called
|
|
49144
|
+
*/
|
|
49145
|
+
const CREATE_SUBJECT_SNAPSHOT_FN = (subject) => {
|
|
49146
|
+
const emitter = subject["_emitter"];
|
|
49147
|
+
const events = emitter["_events"];
|
|
49148
|
+
emitter["_events"] = {};
|
|
49149
|
+
return () => {
|
|
49150
|
+
emitter["_events"] = events;
|
|
49151
|
+
};
|
|
49152
|
+
};
|
|
49153
|
+
/**
|
|
49154
|
+
* Manages isolation of global event-bus state between backtest sessions.
|
|
49155
|
+
* Allows temporarily detaching all subject subscriptions so that one session
|
|
49156
|
+
* does not interfere with another, then restoring them afterwards.
|
|
49157
|
+
*/
|
|
49158
|
+
class SessionUtils {
|
|
49159
|
+
constructor() {
|
|
49160
|
+
/**
|
|
49161
|
+
* Snapshots the current listener state of every global subject by replacing
|
|
49162
|
+
* their internal `_events` map with an empty object.
|
|
49163
|
+
* @returns A restore function that, when called, puts all original listeners back.
|
|
49164
|
+
*/
|
|
49165
|
+
this.createSnapshot = () => {
|
|
49166
|
+
backtest.loggerService.log(METHOD_NAME_CREATE_SNAPSHOT);
|
|
49167
|
+
const snapshotList = SUBJECT_ISOLATION_LIST.map(CREATE_SUBJECT_SNAPSHOT_FN);
|
|
49168
|
+
return functoolsKit.compose(...snapshotList);
|
|
49169
|
+
};
|
|
49170
|
+
}
|
|
49171
|
+
}
|
|
49172
|
+
const Session = new SessionUtils();
|
|
49173
|
+
|
|
48183
49174
|
const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
|
|
48184
49175
|
const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
|
|
48185
49176
|
const SCHEDULE_METHOD_NAME_DUMP = "ScheduleUtils.dump";
|
|
@@ -49755,6 +50746,741 @@ class MaxDrawdownUtils {
|
|
|
49755
50746
|
*/
|
|
49756
50747
|
const MaxDrawdown = new MaxDrawdownUtils();
|
|
49757
50748
|
|
|
50749
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT = "ReflectUtils.getPositionPnlPercent";
|
|
50750
|
+
const REFLECT_METHOD_NAME_GET_POSITION_PNL_COST = "ReflectUtils.getPositionPnlCost";
|
|
50751
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "ReflectUtils.getPositionHighestProfitPrice";
|
|
50752
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.getPositionHighestProfitTimestamp";
|
|
50753
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
|
|
50754
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
|
|
50755
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
|
|
50756
|
+
const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
|
|
50757
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
|
|
50758
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
|
|
50759
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "ReflectUtils.getPositionMaxDrawdownPrice";
|
|
50760
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "ReflectUtils.getPositionMaxDrawdownTimestamp";
|
|
50761
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionMaxDrawdownPnlPercentage";
|
|
50762
|
+
const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionMaxDrawdownPnlCost";
|
|
50763
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
50764
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "ReflectUtils.getPositionHighestProfitDistancePnlCost";
|
|
50765
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
50766
|
+
const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
50767
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getMaxDrawdownDistancePnlPercentage";
|
|
50768
|
+
const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "ReflectUtils.getMaxDrawdownDistancePnlCost";
|
|
50769
|
+
/**
|
|
50770
|
+
* Utility class for real-time position reflection: PNL, peak profit, and drawdown queries.
|
|
50771
|
+
*
|
|
50772
|
+
* Provides unified access to strategyCoreService position state methods with logging
|
|
50773
|
+
* and full validation (strategy, exchange, frame, risk, actions).
|
|
50774
|
+
* Works for both live and backtest modes via the `backtest` parameter.
|
|
50775
|
+
* Exported as singleton instance for convenient usage.
|
|
50776
|
+
*
|
|
50777
|
+
* @example
|
|
50778
|
+
* ```typescript
|
|
50779
|
+
* import { Reflect } from "backtest-kit";
|
|
50780
|
+
*
|
|
50781
|
+
* // Get current unrealized PNL percentage
|
|
50782
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50783
|
+
* "BTCUSDT",
|
|
50784
|
+
* 45000,
|
|
50785
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50786
|
+
* );
|
|
50787
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50788
|
+
*
|
|
50789
|
+
* // Get peak profit reached
|
|
50790
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50791
|
+
* "BTCUSDT",
|
|
50792
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50793
|
+
* );
|
|
50794
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50795
|
+
* ```
|
|
50796
|
+
*/
|
|
50797
|
+
class ReflectUtils {
|
|
50798
|
+
constructor() {
|
|
50799
|
+
/**
|
|
50800
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
50801
|
+
*
|
|
50802
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50803
|
+
* Returns null if no pending signal exists.
|
|
50804
|
+
*
|
|
50805
|
+
* @param symbol - Trading pair symbol
|
|
50806
|
+
* @param currentPrice - Current market price
|
|
50807
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50808
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50809
|
+
* @returns Promise resolving to PNL percentage or null
|
|
50810
|
+
*
|
|
50811
|
+
* @example
|
|
50812
|
+
* ```typescript
|
|
50813
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
50814
|
+
* "BTCUSDT",
|
|
50815
|
+
* 45000,
|
|
50816
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50817
|
+
* );
|
|
50818
|
+
* console.log(`PNL: ${pnl}%`);
|
|
50819
|
+
* ```
|
|
50820
|
+
*/
|
|
50821
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50822
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
|
|
50823
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50824
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50825
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50826
|
+
{
|
|
50827
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50828
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
50829
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50830
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
50831
|
+
}
|
|
50832
|
+
return await backtest.strategyCoreService.getPositionPnlPercent(backtest$1, symbol, currentPrice, context);
|
|
50833
|
+
};
|
|
50834
|
+
/**
|
|
50835
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
50836
|
+
*
|
|
50837
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost.
|
|
50838
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
50839
|
+
* Returns null if no pending signal exists.
|
|
50840
|
+
*
|
|
50841
|
+
* @param symbol - Trading pair symbol
|
|
50842
|
+
* @param currentPrice - Current market price
|
|
50843
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50844
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50845
|
+
* @returns Promise resolving to PNL in dollars or null
|
|
50846
|
+
*
|
|
50847
|
+
* @example
|
|
50848
|
+
* ```typescript
|
|
50849
|
+
* const pnlCost = await Reflect.getPositionPnlCost(
|
|
50850
|
+
* "BTCUSDT",
|
|
50851
|
+
* 45000,
|
|
50852
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50853
|
+
* );
|
|
50854
|
+
* console.log(`PNL: $${pnlCost}`);
|
|
50855
|
+
* ```
|
|
50856
|
+
*/
|
|
50857
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context, backtest$1 = false) => {
|
|
50858
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
|
|
50859
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50860
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50861
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50862
|
+
{
|
|
50863
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50864
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
50865
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50866
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
50867
|
+
}
|
|
50868
|
+
return await backtest.strategyCoreService.getPositionPnlCost(backtest$1, symbol, currentPrice, context);
|
|
50869
|
+
};
|
|
50870
|
+
/**
|
|
50871
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
50872
|
+
*
|
|
50873
|
+
* Returns null if no pending signal exists.
|
|
50874
|
+
*
|
|
50875
|
+
* @param symbol - Trading pair symbol
|
|
50876
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50877
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50878
|
+
* @returns Promise resolving to price or null
|
|
50879
|
+
*
|
|
50880
|
+
* @example
|
|
50881
|
+
* ```typescript
|
|
50882
|
+
* const peakPrice = await Reflect.getPositionHighestProfitPrice(
|
|
50883
|
+
* "BTCUSDT",
|
|
50884
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50885
|
+
* );
|
|
50886
|
+
* console.log(`Peak price: ${peakPrice}`);
|
|
50887
|
+
* ```
|
|
50888
|
+
*/
|
|
50889
|
+
this.getPositionHighestProfitPrice = async (symbol, context, backtest$1 = false) => {
|
|
50890
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, { symbol, context });
|
|
50891
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50892
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50893
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50894
|
+
{
|
|
50895
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50896
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
50897
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50898
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
50899
|
+
}
|
|
50900
|
+
return await backtest.strategyCoreService.getPositionHighestProfitPrice(backtest$1, symbol, context);
|
|
50901
|
+
};
|
|
50902
|
+
/**
|
|
50903
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
50904
|
+
*
|
|
50905
|
+
* Returns null if no pending signal exists.
|
|
50906
|
+
*
|
|
50907
|
+
* @param symbol - Trading pair symbol
|
|
50908
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50909
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50910
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
50911
|
+
*
|
|
50912
|
+
* @example
|
|
50913
|
+
* ```typescript
|
|
50914
|
+
* const ts = await Reflect.getPositionHighestProfitTimestamp(
|
|
50915
|
+
* "BTCUSDT",
|
|
50916
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50917
|
+
* );
|
|
50918
|
+
* console.log(`Peak at: ${new Date(ts).toISOString()}`);
|
|
50919
|
+
* ```
|
|
50920
|
+
*/
|
|
50921
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
50922
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, { symbol, context });
|
|
50923
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50924
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50925
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50926
|
+
{
|
|
50927
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50928
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
50929
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50930
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
50931
|
+
}
|
|
50932
|
+
return await backtest.strategyCoreService.getPositionHighestProfitTimestamp(backtest$1, symbol, context);
|
|
50933
|
+
};
|
|
50934
|
+
/**
|
|
50935
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
50936
|
+
*
|
|
50937
|
+
* Returns null if no pending signal exists.
|
|
50938
|
+
*
|
|
50939
|
+
* @param symbol - Trading pair symbol
|
|
50940
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50941
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50942
|
+
* @returns Promise resolving to PnL percentage or null
|
|
50943
|
+
*
|
|
50944
|
+
* @example
|
|
50945
|
+
* ```typescript
|
|
50946
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
50947
|
+
* "BTCUSDT",
|
|
50948
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50949
|
+
* );
|
|
50950
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
50951
|
+
* ```
|
|
50952
|
+
*/
|
|
50953
|
+
this.getPositionHighestPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
50954
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, { symbol, context });
|
|
50955
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50956
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50957
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50958
|
+
{
|
|
50959
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50960
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
50961
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50962
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
50963
|
+
}
|
|
50964
|
+
return await backtest.strategyCoreService.getPositionHighestPnlPercentage(backtest$1, symbol, context);
|
|
50965
|
+
};
|
|
50966
|
+
/**
|
|
50967
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
50968
|
+
*
|
|
50969
|
+
* Returns null if no pending signal exists.
|
|
50970
|
+
*
|
|
50971
|
+
* @param symbol - Trading pair symbol
|
|
50972
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
50973
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
50974
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
50975
|
+
*
|
|
50976
|
+
* @example
|
|
50977
|
+
* ```typescript
|
|
50978
|
+
* const peakCost = await Reflect.getPositionHighestPnlCost(
|
|
50979
|
+
* "BTCUSDT",
|
|
50980
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
50981
|
+
* );
|
|
50982
|
+
* console.log(`Peak PNL: $${peakCost}`);
|
|
50983
|
+
* ```
|
|
50984
|
+
*/
|
|
50985
|
+
this.getPositionHighestPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
50986
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, { symbol, context });
|
|
50987
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50988
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50989
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50990
|
+
{
|
|
50991
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
50992
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
50993
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50994
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
50995
|
+
}
|
|
50996
|
+
return await backtest.strategyCoreService.getPositionHighestPnlCost(backtest$1, symbol, context);
|
|
50997
|
+
};
|
|
50998
|
+
/**
|
|
50999
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
51000
|
+
*
|
|
51001
|
+
* Returns null if no pending signal exists.
|
|
51002
|
+
*
|
|
51003
|
+
* @param symbol - Trading pair symbol
|
|
51004
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51005
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51006
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
51007
|
+
*
|
|
51008
|
+
* @example
|
|
51009
|
+
* ```typescript
|
|
51010
|
+
* const wasReachable = await Reflect.getPositionHighestProfitBreakeven(
|
|
51011
|
+
* "BTCUSDT",
|
|
51012
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51013
|
+
* );
|
|
51014
|
+
* console.log(`Breakeven reachable at peak: ${wasReachable}`);
|
|
51015
|
+
* ```
|
|
51016
|
+
*/
|
|
51017
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context, backtest$1 = false) => {
|
|
51018
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, { symbol, context });
|
|
51019
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51020
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51021
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51022
|
+
{
|
|
51023
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51024
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
51025
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
51026
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
51027
|
+
}
|
|
51028
|
+
return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
|
|
51029
|
+
};
|
|
51030
|
+
/**
|
|
51031
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
51032
|
+
*
|
|
51033
|
+
* Returns null if no pending signal exists.
|
|
51034
|
+
*
|
|
51035
|
+
* @param symbol - Trading pair symbol
|
|
51036
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51037
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51038
|
+
* @returns Promise resolving to minutes since highest profit price was recorded, or null
|
|
51039
|
+
*
|
|
51040
|
+
* @example
|
|
51041
|
+
* ```typescript
|
|
51042
|
+
* const minutes = await Reflect.getPositionDrawdownMinutes(
|
|
51043
|
+
* "BTCUSDT",
|
|
51044
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51045
|
+
* );
|
|
51046
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
51047
|
+
* ```
|
|
51048
|
+
*/
|
|
51049
|
+
this.getPositionDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
51050
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, { symbol, context });
|
|
51051
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51052
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51053
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51054
|
+
{
|
|
51055
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51056
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
51057
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
51058
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
51059
|
+
}
|
|
51060
|
+
return await backtest.strategyCoreService.getPositionDrawdownMinutes(backtest$1, symbol, context);
|
|
51061
|
+
};
|
|
51062
|
+
/**
|
|
51063
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
51064
|
+
*
|
|
51065
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
51066
|
+
* pulling back from its peak profit level.
|
|
51067
|
+
* Returns null if no pending signal exists.
|
|
51068
|
+
*
|
|
51069
|
+
* @param symbol - Trading pair symbol
|
|
51070
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51071
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51072
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
51073
|
+
*
|
|
51074
|
+
* @example
|
|
51075
|
+
* ```typescript
|
|
51076
|
+
* const minutes = await Reflect.getPositionHighestProfitMinutes(
|
|
51077
|
+
* "BTCUSDT",
|
|
51078
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51079
|
+
* );
|
|
51080
|
+
* console.log(`Pulling back from peak for ${minutes} minutes`);
|
|
51081
|
+
* ```
|
|
51082
|
+
*/
|
|
51083
|
+
this.getPositionHighestProfitMinutes = async (symbol, context, backtest$1 = false) => {
|
|
51084
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, { symbol, context });
|
|
51085
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51086
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51087
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51088
|
+
{
|
|
51089
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51090
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
51091
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
51092
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
51093
|
+
}
|
|
51094
|
+
return await backtest.strategyCoreService.getPositionHighestProfitMinutes(backtest$1, symbol, context);
|
|
51095
|
+
};
|
|
51096
|
+
/**
|
|
51097
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
51098
|
+
*
|
|
51099
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
51100
|
+
* Zero when called at the exact moment the trough was set.
|
|
51101
|
+
* Returns null if no pending signal exists.
|
|
51102
|
+
*
|
|
51103
|
+
* @param symbol - Trading pair symbol
|
|
51104
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51105
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51106
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
51107
|
+
*
|
|
51108
|
+
* @example
|
|
51109
|
+
* ```typescript
|
|
51110
|
+
* const minutes = await Reflect.getPositionMaxDrawdownMinutes(
|
|
51111
|
+
* "BTCUSDT",
|
|
51112
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51113
|
+
* );
|
|
51114
|
+
* console.log(`Drawdown trough was ${minutes} minutes ago`);
|
|
51115
|
+
* ```
|
|
51116
|
+
*/
|
|
51117
|
+
this.getPositionMaxDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
|
|
51118
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, { symbol, context });
|
|
51119
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51120
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51121
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51122
|
+
{
|
|
51123
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51124
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
51125
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
51126
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
51127
|
+
}
|
|
51128
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownMinutes(backtest$1, symbol, context);
|
|
51129
|
+
};
|
|
51130
|
+
/**
|
|
51131
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
51132
|
+
*
|
|
51133
|
+
* Returns null if no pending signal exists.
|
|
51134
|
+
*
|
|
51135
|
+
* @param symbol - Trading pair symbol
|
|
51136
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51137
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51138
|
+
* @returns Promise resolving to price or null
|
|
51139
|
+
*
|
|
51140
|
+
* @example
|
|
51141
|
+
* ```typescript
|
|
51142
|
+
* const troughPrice = await Reflect.getPositionMaxDrawdownPrice(
|
|
51143
|
+
* "BTCUSDT",
|
|
51144
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51145
|
+
* );
|
|
51146
|
+
* console.log(`Worst price: ${troughPrice}`);
|
|
51147
|
+
* ```
|
|
51148
|
+
*/
|
|
51149
|
+
this.getPositionMaxDrawdownPrice = async (symbol, context, backtest$1 = false) => {
|
|
51150
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, { symbol, context });
|
|
51151
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51152
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51153
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51154
|
+
{
|
|
51155
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51156
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
51157
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
51158
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
51159
|
+
}
|
|
51160
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPrice(backtest$1, symbol, context);
|
|
51161
|
+
};
|
|
51162
|
+
/**
|
|
51163
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
51164
|
+
*
|
|
51165
|
+
* Returns null if no pending signal exists.
|
|
51166
|
+
*
|
|
51167
|
+
* @param symbol - Trading pair symbol
|
|
51168
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51169
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51170
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
51171
|
+
*
|
|
51172
|
+
* @example
|
|
51173
|
+
* ```typescript
|
|
51174
|
+
* const ts = await Reflect.getPositionMaxDrawdownTimestamp(
|
|
51175
|
+
* "BTCUSDT",
|
|
51176
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51177
|
+
* );
|
|
51178
|
+
* console.log(`Worst drawdown at: ${new Date(ts).toISOString()}`);
|
|
51179
|
+
* ```
|
|
51180
|
+
*/
|
|
51181
|
+
this.getPositionMaxDrawdownTimestamp = async (symbol, context, backtest$1 = false) => {
|
|
51182
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, { symbol, context });
|
|
51183
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51184
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51185
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51186
|
+
{
|
|
51187
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51188
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
51189
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
51190
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
51191
|
+
}
|
|
51192
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownTimestamp(backtest$1, symbol, context);
|
|
51193
|
+
};
|
|
51194
|
+
/**
|
|
51195
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
51196
|
+
*
|
|
51197
|
+
* Returns null if no pending signal exists.
|
|
51198
|
+
*
|
|
51199
|
+
* @param symbol - Trading pair symbol
|
|
51200
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51201
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51202
|
+
* @returns Promise resolving to PnL percentage or null
|
|
51203
|
+
*
|
|
51204
|
+
* @example
|
|
51205
|
+
* ```typescript
|
|
51206
|
+
* const worstPnl = await Reflect.getPositionMaxDrawdownPnlPercentage(
|
|
51207
|
+
* "BTCUSDT",
|
|
51208
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51209
|
+
* );
|
|
51210
|
+
* console.log(`Worst PNL: ${worstPnl}%`);
|
|
51211
|
+
* ```
|
|
51212
|
+
*/
|
|
51213
|
+
this.getPositionMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51214
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
51215
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51216
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51217
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51218
|
+
{
|
|
51219
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51220
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51221
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51222
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51223
|
+
}
|
|
51224
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
51225
|
+
};
|
|
51226
|
+
/**
|
|
51227
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
51228
|
+
*
|
|
51229
|
+
* Returns null if no pending signal exists.
|
|
51230
|
+
*
|
|
51231
|
+
* @param symbol - Trading pair symbol
|
|
51232
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51233
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51234
|
+
* @returns Promise resolving to PnL cost in quote currency or null
|
|
51235
|
+
*
|
|
51236
|
+
* @example
|
|
51237
|
+
* ```typescript
|
|
51238
|
+
* const worstCost = await Reflect.getPositionMaxDrawdownPnlCost(
|
|
51239
|
+
* "BTCUSDT",
|
|
51240
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51241
|
+
* );
|
|
51242
|
+
* console.log(`Worst PNL: $${worstCost}`);
|
|
51243
|
+
* ```
|
|
51244
|
+
*/
|
|
51245
|
+
this.getPositionMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51246
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
51247
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51248
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51249
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51250
|
+
{
|
|
51251
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51252
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
51253
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
51254
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
51255
|
+
}
|
|
51256
|
+
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
51257
|
+
};
|
|
51258
|
+
/**
|
|
51259
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
51260
|
+
*
|
|
51261
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51262
|
+
*
|
|
51263
|
+
* @param symbol - Trading pair symbol
|
|
51264
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51265
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51266
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
51267
|
+
*
|
|
51268
|
+
* @example
|
|
51269
|
+
* ```typescript
|
|
51270
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
51271
|
+
* "BTCUSDT",
|
|
51272
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51273
|
+
* );
|
|
51274
|
+
* console.log(`Dropped ${distance}% from peak`);
|
|
51275
|
+
* ```
|
|
51276
|
+
*/
|
|
51277
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51278
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
51279
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51280
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51281
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51282
|
+
{
|
|
51283
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51284
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
51285
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
51286
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
51287
|
+
}
|
|
51288
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(backtest$1, symbol, context);
|
|
51289
|
+
};
|
|
51290
|
+
/**
|
|
51291
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
51292
|
+
*
|
|
51293
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51294
|
+
*
|
|
51295
|
+
* @param symbol - Trading pair symbol
|
|
51296
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51297
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51298
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
51299
|
+
*
|
|
51300
|
+
* @example
|
|
51301
|
+
* ```typescript
|
|
51302
|
+
* const distance = await Reflect.getPositionHighestProfitDistancePnlCost(
|
|
51303
|
+
* "BTCUSDT",
|
|
51304
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51305
|
+
* );
|
|
51306
|
+
* console.log(`Dropped $${distance} from peak`);
|
|
51307
|
+
* ```
|
|
51308
|
+
*/
|
|
51309
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51310
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, { symbol, context });
|
|
51311
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51312
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51313
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51314
|
+
{
|
|
51315
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51316
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
51317
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
51318
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
51319
|
+
}
|
|
51320
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(backtest$1, symbol, context);
|
|
51321
|
+
};
|
|
51322
|
+
/**
|
|
51323
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
51324
|
+
*
|
|
51325
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51326
|
+
*
|
|
51327
|
+
* @param symbol - Trading pair symbol
|
|
51328
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51329
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51330
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL% (≥ 0) or null
|
|
51331
|
+
*
|
|
51332
|
+
* @example
|
|
51333
|
+
* ```typescript
|
|
51334
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlPercentage(
|
|
51335
|
+
* "BTCUSDT",
|
|
51336
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51337
|
+
* );
|
|
51338
|
+
* console.log(`${distance}% above worst trough`);
|
|
51339
|
+
* ```
|
|
51340
|
+
*/
|
|
51341
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51342
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
|
|
51343
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51344
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51345
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51346
|
+
{
|
|
51347
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51348
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
51349
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51350
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
51351
|
+
}
|
|
51352
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(backtest$1, symbol, context);
|
|
51353
|
+
};
|
|
51354
|
+
/**
|
|
51355
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
51356
|
+
*
|
|
51357
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51358
|
+
*
|
|
51359
|
+
* @param symbol - Trading pair symbol
|
|
51360
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51361
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51362
|
+
* @returns Promise resolving to recovery distance from worst drawdown trough in PnL cost (≥ 0) or null
|
|
51363
|
+
*
|
|
51364
|
+
* @example
|
|
51365
|
+
* ```typescript
|
|
51366
|
+
* const distance = await Reflect.getPositionHighestMaxDrawdownPnlCost(
|
|
51367
|
+
* "BTCUSDT",
|
|
51368
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51369
|
+
* );
|
|
51370
|
+
* console.log(`$${distance} above worst trough`);
|
|
51371
|
+
* ```
|
|
51372
|
+
*/
|
|
51373
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51374
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, { symbol, context });
|
|
51375
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51376
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51377
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51378
|
+
{
|
|
51379
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51380
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
51381
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
51382
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
51383
|
+
}
|
|
51384
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(backtest$1, symbol, context);
|
|
51385
|
+
};
|
|
51386
|
+
/**
|
|
51387
|
+
* Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
|
|
51388
|
+
*
|
|
51389
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51390
|
+
*
|
|
51391
|
+
* @param symbol - Trading pair symbol
|
|
51392
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51393
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51394
|
+
* @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
|
|
51395
|
+
*
|
|
51396
|
+
* @example
|
|
51397
|
+
* ```typescript
|
|
51398
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlPercentage(
|
|
51399
|
+
* "BTCUSDT",
|
|
51400
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51401
|
+
* );
|
|
51402
|
+
* console.log(`Peak-to-trough: ${distance}%`);
|
|
51403
|
+
* ```
|
|
51404
|
+
*/
|
|
51405
|
+
this.getMaxDrawdownDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
|
|
51406
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, { symbol, context });
|
|
51407
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51408
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51409
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51410
|
+
{
|
|
51411
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51412
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
|
|
51413
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
51414
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
|
|
51415
|
+
}
|
|
51416
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(backtest$1, symbol, context);
|
|
51417
|
+
};
|
|
51418
|
+
/**
|
|
51419
|
+
* Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
|
|
51420
|
+
*
|
|
51421
|
+
* Result is ≥ 0. Returns null if no pending signal exists.
|
|
51422
|
+
*
|
|
51423
|
+
* @param symbol - Trading pair symbol
|
|
51424
|
+
* @param context - Execution context with strategyName, exchangeName and frameName
|
|
51425
|
+
* @param backtest - True if backtest mode, false if live mode (default: false)
|
|
51426
|
+
* @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
|
|
51427
|
+
*
|
|
51428
|
+
* @example
|
|
51429
|
+
* ```typescript
|
|
51430
|
+
* const distance = await Reflect.getMaxDrawdownDistancePnlCost(
|
|
51431
|
+
* "BTCUSDT",
|
|
51432
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51433
|
+
* );
|
|
51434
|
+
* console.log(`Peak-to-trough: $${distance}`);
|
|
51435
|
+
* ```
|
|
51436
|
+
*/
|
|
51437
|
+
this.getMaxDrawdownDistancePnlCost = async (symbol, context, backtest$1 = false) => {
|
|
51438
|
+
backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, { symbol, context });
|
|
51439
|
+
backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51440
|
+
backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51441
|
+
context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51442
|
+
{
|
|
51443
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
51444
|
+
riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
|
|
51445
|
+
riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
51446
|
+
actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
|
|
51447
|
+
}
|
|
51448
|
+
return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(backtest$1, symbol, context);
|
|
51449
|
+
};
|
|
51450
|
+
}
|
|
51451
|
+
}
|
|
51452
|
+
/**
|
|
51453
|
+
* Singleton instance of ReflectUtils for convenient position state queries.
|
|
51454
|
+
*
|
|
51455
|
+
* @example
|
|
51456
|
+
* ```typescript
|
|
51457
|
+
* import { Reflect } from "backtest-kit";
|
|
51458
|
+
*
|
|
51459
|
+
* // Real-time PNL
|
|
51460
|
+
* const pnl = await Reflect.getPositionPnlPercent(
|
|
51461
|
+
* "BTCUSDT",
|
|
51462
|
+
* 45000,
|
|
51463
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51464
|
+
* );
|
|
51465
|
+
* console.log(`PNL: ${pnl}%`);
|
|
51466
|
+
*
|
|
51467
|
+
* // Peak profit
|
|
51468
|
+
* const peakPnl = await Reflect.getPositionHighestPnlPercentage(
|
|
51469
|
+
* "BTCUSDT",
|
|
51470
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51471
|
+
* );
|
|
51472
|
+
* console.log(`Peak PNL: ${peakPnl}%`);
|
|
51473
|
+
*
|
|
51474
|
+
* // Drawdown from peak
|
|
51475
|
+
* const drawdown = await Reflect.getPositionHighestProfitDistancePnlPercentage(
|
|
51476
|
+
* "BTCUSDT",
|
|
51477
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
|
|
51478
|
+
* );
|
|
51479
|
+
* console.log(`Dropped ${drawdown}% from peak`);
|
|
51480
|
+
* ```
|
|
51481
|
+
*/
|
|
51482
|
+
const Reflect$1 = new ReflectUtils();
|
|
51483
|
+
|
|
49758
51484
|
/**
|
|
49759
51485
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
49760
51486
|
*
|
|
@@ -53682,12 +55408,15 @@ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
|
|
|
53682
55408
|
const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
|
|
53683
55409
|
const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
|
|
53684
55410
|
const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
|
|
55411
|
+
const CACHE_METHOD_NAME_FN_HAS_VALUE = "CacheUtils.fn.hasValue";
|
|
53685
55412
|
const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
|
|
53686
55413
|
const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
|
|
55414
|
+
const CACHE_METHOD_NAME_FILE_HAS_VALUE = "CacheUtils.file.hasValue";
|
|
53687
55415
|
const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
|
|
53688
55416
|
const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
|
|
53689
55417
|
const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
|
|
53690
55418
|
const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
|
|
55419
|
+
const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
|
|
53691
55420
|
const MS_PER_MINUTE$1 = 60000;
|
|
53692
55421
|
const INTERVAL_MINUTES$1 = {
|
|
53693
55422
|
"1m": 1,
|
|
@@ -53877,6 +55606,36 @@ class CacheFnInstance {
|
|
|
53877
55606
|
}
|
|
53878
55607
|
}
|
|
53879
55608
|
};
|
|
55609
|
+
/**
|
|
55610
|
+
* Check whether a valid (non-expired) cache entry exists for the current context and arguments.
|
|
55611
|
+
*
|
|
55612
|
+
* Returns `true` if a cached value exists and its interval is still current.
|
|
55613
|
+
* Returns `false` if there is no entry or the cached entry has expired.
|
|
55614
|
+
*
|
|
55615
|
+
* Requires active execution context and method context.
|
|
55616
|
+
*
|
|
55617
|
+
* @param args - Arguments to look up in the cache
|
|
55618
|
+
* @returns `true` if a fresh cached value exists, `false` otherwise
|
|
55619
|
+
*/
|
|
55620
|
+
this.hasValue = (...args) => {
|
|
55621
|
+
if (!MethodContextService.hasContext()) {
|
|
55622
|
+
throw new Error("CacheFnInstance hasValue requires method context");
|
|
55623
|
+
}
|
|
55624
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55625
|
+
throw new Error("CacheFnInstance hasValue requires execution context");
|
|
55626
|
+
}
|
|
55627
|
+
const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
55628
|
+
const argKey = String(this.key(args));
|
|
55629
|
+
const key = `${contextKey}:${argKey}`;
|
|
55630
|
+
const cached = this._cacheMap.get(key);
|
|
55631
|
+
if (!cached) {
|
|
55632
|
+
return false;
|
|
55633
|
+
}
|
|
55634
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
55635
|
+
const currentAligned = align$1(currentWhen.getTime(), this.interval);
|
|
55636
|
+
const cachedAligned = align$1(cached.when.getTime(), this.interval);
|
|
55637
|
+
return currentAligned === cachedAligned;
|
|
55638
|
+
};
|
|
53880
55639
|
/**
|
|
53881
55640
|
* Garbage collect expired cache entries.
|
|
53882
55641
|
*
|
|
@@ -54001,6 +55760,33 @@ class CacheFileInstance {
|
|
|
54001
55760
|
await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
54002
55761
|
return result;
|
|
54003
55762
|
};
|
|
55763
|
+
/**
|
|
55764
|
+
* Check whether a cached value exists on disk for the given arguments and current interval.
|
|
55765
|
+
*
|
|
55766
|
+
* Returns `true` if a persisted record exists for the current aligned timestamp.
|
|
55767
|
+
* Returns `false` if no record is found.
|
|
55768
|
+
*
|
|
55769
|
+
* Requires active execution context and method context.
|
|
55770
|
+
*
|
|
55771
|
+
* @param args - Arguments forwarded to the key generator
|
|
55772
|
+
* @returns `true` if a cached record exists, `false` otherwise
|
|
55773
|
+
*/
|
|
55774
|
+
this.hasValue = async (...args) => {
|
|
55775
|
+
backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
|
|
55776
|
+
if (!MethodContextService.hasContext()) {
|
|
55777
|
+
throw new Error("CacheFileInstance hasValue requires method context");
|
|
55778
|
+
}
|
|
55779
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55780
|
+
throw new Error("CacheFileInstance hasValue requires execution context");
|
|
55781
|
+
}
|
|
55782
|
+
const [symbol, ...rest] = args;
|
|
55783
|
+
const { when } = backtest.executionContextService.context;
|
|
55784
|
+
const alignedTs = align$1(when.getTime(), this.interval);
|
|
55785
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
55786
|
+
const entityKey = this.key([symbol, alignedTs, ...rest]);
|
|
55787
|
+
const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
|
|
55788
|
+
return cached !== null;
|
|
55789
|
+
};
|
|
54004
55790
|
/**
|
|
54005
55791
|
* Soft-delete all persisted records for this instance's bucket.
|
|
54006
55792
|
* After this call the next `run()` will recompute and re-cache the value.
|
|
@@ -54087,23 +55873,30 @@ class CacheUtils {
|
|
|
54087
55873
|
wrappedFn.clear = () => {
|
|
54088
55874
|
backtest.loggerService.info(CACHE_METHOD_NAME_FN_CLEAR);
|
|
54089
55875
|
if (!MethodContextService.hasContext()) {
|
|
54090
|
-
|
|
54091
|
-
return;
|
|
55876
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires method context`);
|
|
54092
55877
|
}
|
|
54093
55878
|
if (!ExecutionContextService.hasContext()) {
|
|
54094
|
-
|
|
54095
|
-
return;
|
|
55879
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires execution context`);
|
|
54096
55880
|
}
|
|
54097
55881
|
this._getFnInstance.get(run)?.clear();
|
|
54098
55882
|
};
|
|
54099
55883
|
wrappedFn.gc = () => {
|
|
54100
55884
|
backtest.loggerService.info(CACHE_METHOD_NAME_FN_GC);
|
|
54101
55885
|
if (!ExecutionContextService.hasContext()) {
|
|
54102
|
-
|
|
54103
|
-
return;
|
|
55886
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_GC} requires execution context`);
|
|
54104
55887
|
}
|
|
54105
55888
|
return this._getFnInstance.get(run)?.gc();
|
|
54106
55889
|
};
|
|
55890
|
+
wrappedFn.hasValue = (...args) => {
|
|
55891
|
+
backtest.loggerService.info(CACHE_METHOD_NAME_FN_HAS_VALUE);
|
|
55892
|
+
if (!MethodContextService.hasContext()) {
|
|
55893
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires method context`);
|
|
55894
|
+
}
|
|
55895
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55896
|
+
throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
|
|
55897
|
+
}
|
|
55898
|
+
return this._getFnInstance.get(run)?.hasValue(...args) ?? false;
|
|
55899
|
+
};
|
|
54107
55900
|
return wrappedFn;
|
|
54108
55901
|
};
|
|
54109
55902
|
/**
|
|
@@ -54155,8 +55948,25 @@ class CacheUtils {
|
|
|
54155
55948
|
};
|
|
54156
55949
|
wrappedFn.clear = async () => {
|
|
54157
55950
|
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
|
|
55951
|
+
if (!MethodContextService.hasContext()) {
|
|
55952
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires method context`);
|
|
55953
|
+
}
|
|
55954
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55955
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires execution context`);
|
|
55956
|
+
}
|
|
54158
55957
|
await this._getFileInstance.get(run)?.clear();
|
|
54159
55958
|
};
|
|
55959
|
+
wrappedFn.hasValue = async (...args) => {
|
|
55960
|
+
backtest.loggerService.info(CACHE_METHOD_NAME_FILE_HAS_VALUE);
|
|
55961
|
+
if (!MethodContextService.hasContext()) {
|
|
55962
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
|
|
55963
|
+
}
|
|
55964
|
+
if (!ExecutionContextService.hasContext()) {
|
|
55965
|
+
throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
|
|
55966
|
+
}
|
|
55967
|
+
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
55968
|
+
return await instance.hasValue(...args);
|
|
55969
|
+
};
|
|
54160
55970
|
return wrappedFn;
|
|
54161
55971
|
};
|
|
54162
55972
|
/**
|
|
@@ -54225,11 +56035,14 @@ const Cache = new CacheUtils();
|
|
|
54225
56035
|
|
|
54226
56036
|
const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
|
|
54227
56037
|
const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
|
|
56038
|
+
const INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "IntervalFileInstance.hasValue";
|
|
54228
56039
|
const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
|
|
54229
56040
|
const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
|
|
54230
56041
|
const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
|
|
56042
|
+
const INTERVAL_METHOD_NAME_FN_HAS_VALUE = "IntervalUtils.fn.hasValue";
|
|
54231
56043
|
const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
|
|
54232
56044
|
const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
|
|
56045
|
+
const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
|
|
54233
56046
|
const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
|
|
54234
56047
|
const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
|
|
54235
56048
|
const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
|
|
@@ -54387,6 +56200,31 @@ class IntervalFnInstance {
|
|
|
54387
56200
|
}
|
|
54388
56201
|
}
|
|
54389
56202
|
};
|
|
56203
|
+
/**
|
|
56204
|
+
* Check whether the function has already fired for the current interval and context.
|
|
56205
|
+
*
|
|
56206
|
+
* Returns `true` if the function fired (non-null result) within the current interval boundary.
|
|
56207
|
+
* Returns `false` if there is no recorded firing for this interval.
|
|
56208
|
+
*
|
|
56209
|
+
* Requires active method context and execution context.
|
|
56210
|
+
*
|
|
56211
|
+
* @param args - Arguments to look up in the state map
|
|
56212
|
+
* @returns `true` if the function has already fired this interval, `false` otherwise
|
|
56213
|
+
*/
|
|
56214
|
+
this.hasValue = (...args) => {
|
|
56215
|
+
if (!MethodContextService.hasContext()) {
|
|
56216
|
+
throw new Error("IntervalFnInstance hasValue requires method context");
|
|
56217
|
+
}
|
|
56218
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56219
|
+
throw new Error("IntervalFnInstance hasValue requires execution context");
|
|
56220
|
+
}
|
|
56221
|
+
const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
|
|
56222
|
+
const currentWhen = backtest.executionContextService.context.when;
|
|
56223
|
+
const currentAligned = align(currentWhen.getTime(), this.interval);
|
|
56224
|
+
const argKey = this.key(args);
|
|
56225
|
+
const stateKey = `${contextKey}:${argKey}`;
|
|
56226
|
+
return this._stateMap.get(stateKey) === currentAligned;
|
|
56227
|
+
};
|
|
54390
56228
|
/**
|
|
54391
56229
|
* Garbage collect expired state entries.
|
|
54392
56230
|
*
|
|
@@ -54508,6 +56346,33 @@ class IntervalFileInstance {
|
|
|
54508
56346
|
}
|
|
54509
56347
|
return result;
|
|
54510
56348
|
};
|
|
56349
|
+
/**
|
|
56350
|
+
* Check whether the function has already fired for the current interval on disk.
|
|
56351
|
+
*
|
|
56352
|
+
* Returns `true` if a persisted record exists for the current aligned timestamp.
|
|
56353
|
+
* Returns `false` if no record is found.
|
|
56354
|
+
*
|
|
56355
|
+
* Requires active execution context and method context.
|
|
56356
|
+
*
|
|
56357
|
+
* @param args - Arguments forwarded to the key generator
|
|
56358
|
+
* @returns `true` if a fired record exists, `false` otherwise
|
|
56359
|
+
*/
|
|
56360
|
+
this.hasValue = async (...args) => {
|
|
56361
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
|
|
56362
|
+
if (!MethodContextService.hasContext()) {
|
|
56363
|
+
throw new Error("IntervalFileInstance hasValue requires method context");
|
|
56364
|
+
}
|
|
56365
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56366
|
+
throw new Error("IntervalFileInstance hasValue requires execution context");
|
|
56367
|
+
}
|
|
56368
|
+
const [symbol, ...rest] = args;
|
|
56369
|
+
const { when } = backtest.executionContextService.context;
|
|
56370
|
+
const alignedMs = align(when.getTime(), this.interval);
|
|
56371
|
+
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
56372
|
+
const entityKey = this.key([symbol, alignedMs, ...rest]);
|
|
56373
|
+
const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
|
|
56374
|
+
return cached !== null;
|
|
56375
|
+
};
|
|
54511
56376
|
/**
|
|
54512
56377
|
* Soft-delete all persisted records for this instance's bucket.
|
|
54513
56378
|
* After this call the function will fire again on the next `run()`.
|
|
@@ -54588,23 +56453,30 @@ class IntervalUtils {
|
|
|
54588
56453
|
wrappedFn.clear = () => {
|
|
54589
56454
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
|
|
54590
56455
|
if (!MethodContextService.hasContext()) {
|
|
54591
|
-
|
|
54592
|
-
return;
|
|
56456
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires method context`);
|
|
54593
56457
|
}
|
|
54594
56458
|
if (!ExecutionContextService.hasContext()) {
|
|
54595
|
-
|
|
54596
|
-
return;
|
|
56459
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires execution context`);
|
|
54597
56460
|
}
|
|
54598
56461
|
this._getInstance.get(run)?.clear();
|
|
54599
56462
|
};
|
|
54600
56463
|
wrappedFn.gc = () => {
|
|
54601
56464
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
|
|
54602
56465
|
if (!ExecutionContextService.hasContext()) {
|
|
54603
|
-
|
|
54604
|
-
return;
|
|
56466
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_GC} requires execution context`);
|
|
54605
56467
|
}
|
|
54606
56468
|
return this._getInstance.get(run)?.gc();
|
|
54607
56469
|
};
|
|
56470
|
+
wrappedFn.hasValue = (...args) => {
|
|
56471
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_HAS_VALUE);
|
|
56472
|
+
if (!MethodContextService.hasContext()) {
|
|
56473
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires method context`);
|
|
56474
|
+
}
|
|
56475
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56476
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
|
|
56477
|
+
}
|
|
56478
|
+
return this._getInstance.get(run)?.hasValue(...args) ?? false;
|
|
56479
|
+
};
|
|
54608
56480
|
return wrappedFn;
|
|
54609
56481
|
};
|
|
54610
56482
|
/**
|
|
@@ -54647,8 +56519,25 @@ class IntervalUtils {
|
|
|
54647
56519
|
};
|
|
54648
56520
|
wrappedFn.clear = async () => {
|
|
54649
56521
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
56522
|
+
if (!MethodContextService.hasContext()) {
|
|
56523
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires method context`);
|
|
56524
|
+
}
|
|
56525
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56526
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires execution context`);
|
|
56527
|
+
}
|
|
54650
56528
|
await this._getFileInstance.get(run)?.clear();
|
|
54651
56529
|
};
|
|
56530
|
+
wrappedFn.hasValue = async (...args) => {
|
|
56531
|
+
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_HAS_VALUE);
|
|
56532
|
+
if (!MethodContextService.hasContext()) {
|
|
56533
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
|
|
56534
|
+
}
|
|
56535
|
+
if (!ExecutionContextService.hasContext()) {
|
|
56536
|
+
throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
|
|
56537
|
+
}
|
|
56538
|
+
const instance = this._getFileInstance(run, context.interval, context.name, context.key);
|
|
56539
|
+
return await instance.hasValue(...args);
|
|
56540
|
+
};
|
|
54652
56541
|
return wrappedFn;
|
|
54653
56542
|
};
|
|
54654
56543
|
/**
|
|
@@ -55858,17 +57747,23 @@ exports.PersistMeasureAdapter = PersistMeasureAdapter;
|
|
|
55858
57747
|
exports.PersistMemoryAdapter = PersistMemoryAdapter;
|
|
55859
57748
|
exports.PersistNotificationAdapter = PersistNotificationAdapter;
|
|
55860
57749
|
exports.PersistPartialAdapter = PersistPartialAdapter;
|
|
57750
|
+
exports.PersistRecentAdapter = PersistRecentAdapter;
|
|
55861
57751
|
exports.PersistRiskAdapter = PersistRiskAdapter;
|
|
55862
57752
|
exports.PersistScheduleAdapter = PersistScheduleAdapter;
|
|
55863
57753
|
exports.PersistSignalAdapter = PersistSignalAdapter;
|
|
55864
57754
|
exports.PersistStorageAdapter = PersistStorageAdapter;
|
|
55865
57755
|
exports.Position = Position;
|
|
55866
57756
|
exports.PositionSize = PositionSize;
|
|
57757
|
+
exports.Recent = Recent;
|
|
57758
|
+
exports.RecentBacktest = RecentBacktest;
|
|
57759
|
+
exports.RecentLive = RecentLive;
|
|
57760
|
+
exports.Reflect = Reflect$1;
|
|
55867
57761
|
exports.Report = Report;
|
|
55868
57762
|
exports.ReportBase = ReportBase;
|
|
55869
57763
|
exports.ReportWriter = ReportWriter;
|
|
55870
57764
|
exports.Risk = Risk;
|
|
55871
57765
|
exports.Schedule = Schedule;
|
|
57766
|
+
exports.Session = Session;
|
|
55872
57767
|
exports.Storage = Storage;
|
|
55873
57768
|
exports.StorageBacktest = StorageBacktest;
|
|
55874
57769
|
exports.StorageLive = StorageLive;
|
|
@@ -55922,6 +57817,9 @@ exports.getDefaultConfig = getDefaultConfig;
|
|
|
55922
57817
|
exports.getEffectivePriceOpen = getEffectivePriceOpen;
|
|
55923
57818
|
exports.getExchangeSchema = getExchangeSchema;
|
|
55924
57819
|
exports.getFrameSchema = getFrameSchema;
|
|
57820
|
+
exports.getLatestSignal = getLatestSignal;
|
|
57821
|
+
exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
|
|
57822
|
+
exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
|
|
55925
57823
|
exports.getMode = getMode;
|
|
55926
57824
|
exports.getNextCandles = getNextCandles;
|
|
55927
57825
|
exports.getOrderBook = getOrderBook;
|