backtest-kit 5.5.2 → 5.6.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 +1979 -168
- package/build/index.mjs +1967 -168
- package/package.json +1 -1
- package/types.d.ts +1100 -140
package/build/index.mjs
CHANGED
|
@@ -107,6 +107,7 @@ const markdownServices$1 = {
|
|
|
107
107
|
riskMarkdownService: Symbol('riskMarkdownService'),
|
|
108
108
|
strategyMarkdownService: Symbol('strategyMarkdownService'),
|
|
109
109
|
syncMarkdownService: Symbol('syncMarkdownService'),
|
|
110
|
+
highestProfitMarkdownService: Symbol('highestProfitMarkdownService'),
|
|
110
111
|
};
|
|
111
112
|
const reportServices$1 = {
|
|
112
113
|
backtestReportService: Symbol('backtestReportService'),
|
|
@@ -120,6 +121,7 @@ const reportServices$1 = {
|
|
|
120
121
|
riskReportService: Symbol('riskReportService'),
|
|
121
122
|
strategyReportService: Symbol('strategyReportService'),
|
|
122
123
|
syncReportService: Symbol('syncReportService'),
|
|
124
|
+
highestProfitReportService: Symbol('highestProfitReportService'),
|
|
123
125
|
};
|
|
124
126
|
const validationServices$1 = {
|
|
125
127
|
exchangeValidationService: Symbol('exchangeValidationService'),
|
|
@@ -622,6 +624,12 @@ const strategyCommitSubject = new Subject();
|
|
|
622
624
|
* BacktestLogicPrivateService::*run
|
|
623
625
|
*/
|
|
624
626
|
const backtestScheduleOpenSubject = new Subject();
|
|
627
|
+
/**
|
|
628
|
+
* Highest profit emitter for real-time profit tracking.
|
|
629
|
+
* Emits updates on the highest profit achieved for an open position.
|
|
630
|
+
* Allows users to track profit milestones and implement custom management logic based on profit levels.
|
|
631
|
+
*/
|
|
632
|
+
const highestProfitSubject = new Subject();
|
|
625
633
|
|
|
626
634
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
627
635
|
__proto__: null,
|
|
@@ -633,6 +641,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
633
641
|
doneWalkerSubject: doneWalkerSubject,
|
|
634
642
|
errorEmitter: errorEmitter,
|
|
635
643
|
exitEmitter: exitEmitter,
|
|
644
|
+
highestProfitSubject: highestProfitSubject,
|
|
636
645
|
partialLossSubject: partialLossSubject,
|
|
637
646
|
partialProfitSubject: partialProfitSubject,
|
|
638
647
|
performanceEmitter: performanceEmitter,
|
|
@@ -4251,6 +4260,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4251
4260
|
timestamp: currentTime,
|
|
4252
4261
|
_isScheduled: false,
|
|
4253
4262
|
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4263
|
+
_peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4254
4264
|
};
|
|
4255
4265
|
// Валидируем сигнал перед возвратом
|
|
4256
4266
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -4275,6 +4285,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4275
4285
|
timestamp: currentTime,
|
|
4276
4286
|
_isScheduled: true,
|
|
4277
4287
|
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4288
|
+
_peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4278
4289
|
};
|
|
4279
4290
|
// Валидируем сигнал перед возвратом
|
|
4280
4291
|
VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
|
|
@@ -4295,6 +4306,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4295
4306
|
timestamp: currentTime,
|
|
4296
4307
|
_isScheduled: false,
|
|
4297
4308
|
_entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4309
|
+
_peak: { price: currentPrice, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4298
4310
|
};
|
|
4299
4311
|
// Валидируем сигнал перед возвратом
|
|
4300
4312
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -4997,6 +5009,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
4997
5009
|
...scheduled,
|
|
4998
5010
|
pendingAt: activationTime,
|
|
4999
5011
|
_isScheduled: false,
|
|
5012
|
+
_peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
5000
5013
|
};
|
|
5001
5014
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
5002
5015
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
|
|
@@ -5633,7 +5646,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
|
|
|
5633
5646
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
5634
5647
|
return result;
|
|
5635
5648
|
};
|
|
5636
|
-
const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
5649
|
+
const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backtest) => {
|
|
5637
5650
|
let percentTp = 0;
|
|
5638
5651
|
let percentSl = 0;
|
|
5639
5652
|
const currentTime = self.params.execution.context.when.getTime();
|
|
@@ -5654,6 +5667,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5654
5667
|
const tpDistance = effectiveTakeProfit - effectivePriceOpen;
|
|
5655
5668
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5656
5669
|
percentTp = Math.min(progressPercent, 100);
|
|
5670
|
+
if (currentPrice > signal._peak.price) {
|
|
5671
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
|
|
5672
|
+
signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
5673
|
+
if (self.params.callbacks?.onWrite) {
|
|
5674
|
+
self.params.callbacks.onWrite(signal.symbol, signal, backtest);
|
|
5675
|
+
}
|
|
5676
|
+
!backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
5677
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
|
|
5678
|
+
}
|
|
5657
5679
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
5658
5680
|
}
|
|
5659
5681
|
else if (currentDistance < 0) {
|
|
@@ -5678,6 +5700,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5678
5700
|
const tpDistance = effectivePriceOpen - effectiveTakeProfit;
|
|
5679
5701
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5680
5702
|
percentTp = Math.min(progressPercent, 100);
|
|
5703
|
+
if (currentPrice < signal._peak.price) {
|
|
5704
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
|
|
5705
|
+
signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
5706
|
+
if (self.params.callbacks?.onWrite) {
|
|
5707
|
+
self.params.callbacks.onWrite(signal.symbol, signal, backtest);
|
|
5708
|
+
}
|
|
5709
|
+
!backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
5710
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
|
|
5711
|
+
}
|
|
5681
5712
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
5682
5713
|
}
|
|
5683
5714
|
if (currentDistance < 0) {
|
|
@@ -5725,7 +5756,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
5725
5756
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
5726
5757
|
return result;
|
|
5727
5758
|
};
|
|
5728
|
-
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason) => {
|
|
5759
|
+
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
|
|
5729
5760
|
self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
|
|
5730
5761
|
symbol: self.params.execution.context.symbol,
|
|
5731
5762
|
signalId: scheduled.id,
|
|
@@ -5735,6 +5766,23 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
5735
5766
|
reason,
|
|
5736
5767
|
});
|
|
5737
5768
|
await self.setScheduledSignal(null);
|
|
5769
|
+
if (reason === "user") {
|
|
5770
|
+
await CALL_COMMIT_FN(self, {
|
|
5771
|
+
action: "cancel-scheduled",
|
|
5772
|
+
symbol: self.params.execution.context.symbol,
|
|
5773
|
+
strategyName: self.params.strategyName,
|
|
5774
|
+
exchangeName: self.params.exchangeName,
|
|
5775
|
+
frameName: self.params.frameName,
|
|
5776
|
+
signalId: scheduled.id,
|
|
5777
|
+
backtest: true,
|
|
5778
|
+
cancelId,
|
|
5779
|
+
timestamp: closeTimestamp,
|
|
5780
|
+
totalEntries: scheduled._entry?.length ?? 1,
|
|
5781
|
+
totalPartials: scheduled._partial?.length ?? 0,
|
|
5782
|
+
originalPriceOpen: scheduled.priceOpen,
|
|
5783
|
+
pnl: toProfitLossDto(scheduled, averagePrice),
|
|
5784
|
+
});
|
|
5785
|
+
}
|
|
5738
5786
|
await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
5739
5787
|
const result = {
|
|
5740
5788
|
action: "cancelled",
|
|
@@ -5747,6 +5795,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
5747
5795
|
symbol: self.params.execution.context.symbol,
|
|
5748
5796
|
backtest: self.params.execution.context.backtest,
|
|
5749
5797
|
reason,
|
|
5798
|
+
cancelId,
|
|
5750
5799
|
createdAt: closeTimestamp,
|
|
5751
5800
|
};
|
|
5752
5801
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -5786,6 +5835,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
5786
5835
|
...scheduled,
|
|
5787
5836
|
pendingAt: activationTime,
|
|
5788
5837
|
_isScheduled: false,
|
|
5838
|
+
_peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
5789
5839
|
};
|
|
5790
5840
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
5791
5841
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
|
|
@@ -5935,7 +5985,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
|
|
|
5935
5985
|
// КРИТИЧНО: Проверяем был ли сигнал отменен пользователем через cancel()
|
|
5936
5986
|
if (self._cancelledSignal) {
|
|
5937
5987
|
// Сигнал был отменен через cancel() в onSchedulePing
|
|
5938
|
-
const
|
|
5988
|
+
const cancelId = self._cancelledSignal.cancelId;
|
|
5989
|
+
const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId);
|
|
5939
5990
|
return { outcome: "cancelled", result };
|
|
5940
5991
|
}
|
|
5941
5992
|
// КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
|
|
@@ -5965,6 +6016,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
|
|
|
5965
6016
|
...activatedSignal,
|
|
5966
6017
|
pendingAt: candle.timestamp,
|
|
5967
6018
|
_isScheduled: false,
|
|
6019
|
+
_peak: { price: activatedSignal.priceOpen, timestamp: candle.timestamp, pnlPercentage: 0, pnlCost: 0 },
|
|
5968
6020
|
};
|
|
5969
6021
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
5970
6022
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(candle.timestamp, pendingSignal.priceOpen, pendingSignal, self);
|
|
@@ -6166,6 +6218,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
6166
6218
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
6167
6219
|
const tpDistance = effectiveTakeProfit - effectivePriceOpen;
|
|
6168
6220
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
6221
|
+
if (averagePrice > signal._peak.price) {
|
|
6222
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
|
|
6223
|
+
signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6224
|
+
if (self.params.callbacks?.onWrite) {
|
|
6225
|
+
self.params.callbacks.onWrite(signal.symbol, signal, true);
|
|
6226
|
+
}
|
|
6227
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
|
|
6228
|
+
}
|
|
6169
6229
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
6170
6230
|
}
|
|
6171
6231
|
else if (currentDistance < 0) {
|
|
@@ -6188,6 +6248,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
6188
6248
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
6189
6249
|
const tpDistance = effectivePriceOpen - effectiveTakeProfit;
|
|
6190
6250
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
6251
|
+
if (averagePrice < signal._peak.price) {
|
|
6252
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
|
|
6253
|
+
signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6254
|
+
if (self.params.callbacks?.onWrite) {
|
|
6255
|
+
self.params.callbacks.onWrite(signal.symbol, signal, true);
|
|
6256
|
+
}
|
|
6257
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
|
|
6258
|
+
}
|
|
6191
6259
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
6192
6260
|
}
|
|
6193
6261
|
if (currentDistance < 0) {
|
|
@@ -6564,8 +6632,8 @@ class ClientStrategy {
|
|
|
6564
6632
|
* @param symbol - Trading pair symbol
|
|
6565
6633
|
* @returns Promise resolving to effective entry price or null
|
|
6566
6634
|
*/
|
|
6567
|
-
async
|
|
6568
|
-
this.params.logger.debug("ClientStrategy
|
|
6635
|
+
async getPositionEffectivePrice(symbol) {
|
|
6636
|
+
this.params.logger.debug("ClientStrategy getPositionEffectivePrice", { symbol });
|
|
6569
6637
|
if (!this._pendingSignal) {
|
|
6570
6638
|
return null;
|
|
6571
6639
|
}
|
|
@@ -6722,6 +6790,170 @@ class ClientStrategy {
|
|
|
6722
6790
|
}
|
|
6723
6791
|
return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
|
|
6724
6792
|
}
|
|
6793
|
+
/**
|
|
6794
|
+
* Returns the original estimated duration for the current pending signal.
|
|
6795
|
+
*
|
|
6796
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
6797
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
6798
|
+
*
|
|
6799
|
+
* Returns null if no pending signal exists.
|
|
6800
|
+
*
|
|
6801
|
+
* @param symbol - Trading pair symbol
|
|
6802
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
6803
|
+
*/
|
|
6804
|
+
async getPositionEstimateMinutes(symbol) {
|
|
6805
|
+
this.params.logger.debug("ClientStrategy getPositionEstimateMinutes", { symbol });
|
|
6806
|
+
if (!this._pendingSignal) {
|
|
6807
|
+
return null;
|
|
6808
|
+
}
|
|
6809
|
+
return this._pendingSignal.minuteEstimatedTime;
|
|
6810
|
+
}
|
|
6811
|
+
/**
|
|
6812
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
6813
|
+
*
|
|
6814
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
6815
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
6816
|
+
*
|
|
6817
|
+
* Returns null if no pending signal exists.
|
|
6818
|
+
*
|
|
6819
|
+
* @param symbol - Trading pair symbol
|
|
6820
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
6821
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
6822
|
+
*/
|
|
6823
|
+
async getPositionCountdownMinutes(symbol, timestamp) {
|
|
6824
|
+
this.params.logger.debug("ClientStrategy getPositionCountdownMinutes", { symbol });
|
|
6825
|
+
if (!this._pendingSignal) {
|
|
6826
|
+
return null;
|
|
6827
|
+
}
|
|
6828
|
+
const elapsed = Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
|
|
6829
|
+
return Math.max(0, this._pendingSignal.minuteEstimatedTime - elapsed);
|
|
6830
|
+
}
|
|
6831
|
+
/**
|
|
6832
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
6833
|
+
*
|
|
6834
|
+
* Initialized at position open with the entry price and timestamp.
|
|
6835
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
6836
|
+
* - LONG: tracks the highest price seen above effective entry
|
|
6837
|
+
* - SHORT: tracks the lowest price seen below effective entry
|
|
6838
|
+
*
|
|
6839
|
+
* Returns null if no pending signal exists.
|
|
6840
|
+
* Never returns null when a signal is active — always contains at least the entry price.
|
|
6841
|
+
*
|
|
6842
|
+
* @param symbol - Trading pair symbol
|
|
6843
|
+
* @returns Promise resolving to price or null
|
|
6844
|
+
*/
|
|
6845
|
+
async getPositionHighestProfitPrice(symbol) {
|
|
6846
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitPrice", { symbol });
|
|
6847
|
+
if (!this._pendingSignal) {
|
|
6848
|
+
return null;
|
|
6849
|
+
}
|
|
6850
|
+
return this._pendingSignal._peak.price;
|
|
6851
|
+
}
|
|
6852
|
+
/**
|
|
6853
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
6854
|
+
*
|
|
6855
|
+
* Returns null if no pending signal exists.
|
|
6856
|
+
*
|
|
6857
|
+
* @param symbol - Trading pair symbol
|
|
6858
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
6859
|
+
*/
|
|
6860
|
+
async getPositionHighestProfitTimestamp(symbol) {
|
|
6861
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitTimestamp", { symbol });
|
|
6862
|
+
if (!this._pendingSignal) {
|
|
6863
|
+
return null;
|
|
6864
|
+
}
|
|
6865
|
+
return this._pendingSignal._peak.timestamp;
|
|
6866
|
+
}
|
|
6867
|
+
/**
|
|
6868
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
6869
|
+
*
|
|
6870
|
+
* Initialized at position open with 0.
|
|
6871
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
6872
|
+
* - LONG: tracks the PnL percentage at the highest price seen above effective entry
|
|
6873
|
+
* - SHORT: tracks the PnL percentage at the lowest price seen below effective entry
|
|
6874
|
+
*
|
|
6875
|
+
* Returns null if no pending signal exists.
|
|
6876
|
+
* Never returns null when a signal is active — always contains at least 0.
|
|
6877
|
+
*
|
|
6878
|
+
* @param symbol - Trading pair symbol
|
|
6879
|
+
* @returns Promise resolving to PnL percentage or null
|
|
6880
|
+
*/
|
|
6881
|
+
async getPositionHighestPnlPercentage(symbol) {
|
|
6882
|
+
this.params.logger.debug("ClientStrategy getPositionHighestPnlPercentage", { symbol });
|
|
6883
|
+
if (!this._pendingSignal) {
|
|
6884
|
+
return null;
|
|
6885
|
+
}
|
|
6886
|
+
return this._pendingSignal._peak.pnlPercentage;
|
|
6887
|
+
}
|
|
6888
|
+
/**
|
|
6889
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
6890
|
+
*
|
|
6891
|
+
* Initialized at position open with 0.
|
|
6892
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
6893
|
+
* - LONG: tracks the PnL cost at the highest price seen above effective entry
|
|
6894
|
+
* - SHORT: tracks the PnL cost at the lowest price seen below effective entry
|
|
6895
|
+
*
|
|
6896
|
+
* Returns null if no pending signal exists.
|
|
6897
|
+
* Never returns null when a signal is active — always contains at least 0.
|
|
6898
|
+
*
|
|
6899
|
+
* @param symbol - Trading pair symbol
|
|
6900
|
+
* @returns Promise resolving to PnL cost or null
|
|
6901
|
+
*/
|
|
6902
|
+
async getPositionHighestPnlCost(symbol) {
|
|
6903
|
+
this.params.logger.debug("ClientStrategy getPositionHighestPnlCost", { symbol });
|
|
6904
|
+
if (!this._pendingSignal) {
|
|
6905
|
+
return null;
|
|
6906
|
+
}
|
|
6907
|
+
return this._pendingSignal._peak.pnlCost;
|
|
6908
|
+
}
|
|
6909
|
+
/**
|
|
6910
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
6911
|
+
*
|
|
6912
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
6913
|
+
* Zero when called at the exact moment the peak was set.
|
|
6914
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
6915
|
+
*
|
|
6916
|
+
* Returns null if no pending signal exists.
|
|
6917
|
+
*
|
|
6918
|
+
* @param symbol - Trading pair symbol
|
|
6919
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
6920
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
6921
|
+
*/
|
|
6922
|
+
async getPositionHighestProfitBreakeven(symbol) {
|
|
6923
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitBreakeven", { symbol });
|
|
6924
|
+
if (!this._pendingSignal) {
|
|
6925
|
+
return null;
|
|
6926
|
+
}
|
|
6927
|
+
const signal = this._pendingSignal;
|
|
6928
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
6929
|
+
const peakPrice = signal._peak.price;
|
|
6930
|
+
const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2 + GLOBAL_CONFIG.CC_BREAKEVEN_THRESHOLD;
|
|
6931
|
+
if (signal.position === "long") {
|
|
6932
|
+
return peakPrice >= effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
|
|
6933
|
+
}
|
|
6934
|
+
else {
|
|
6935
|
+
return peakPrice <= effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
|
|
6936
|
+
}
|
|
6937
|
+
}
|
|
6938
|
+
/**
|
|
6939
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
6940
|
+
*
|
|
6941
|
+
* Measures how long the position has been pulling back from its peak.
|
|
6942
|
+
* Zero when called at the exact moment the peak was set.
|
|
6943
|
+
*
|
|
6944
|
+
* Returns null if no pending signal exists.
|
|
6945
|
+
*
|
|
6946
|
+
* @param symbol - Trading pair symbol
|
|
6947
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
6948
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
6949
|
+
*/
|
|
6950
|
+
async getPositionDrawdownMinutes(symbol, timestamp) {
|
|
6951
|
+
this.params.logger.debug("ClientStrategy getPositionDrawdownMinutes", { symbol });
|
|
6952
|
+
if (!this._pendingSignal) {
|
|
6953
|
+
return null;
|
|
6954
|
+
}
|
|
6955
|
+
return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
|
|
6956
|
+
}
|
|
6725
6957
|
/**
|
|
6726
6958
|
* Performs a single tick of strategy execution.
|
|
6727
6959
|
*
|
|
@@ -6897,6 +7129,7 @@ class ClientStrategy {
|
|
|
6897
7129
|
...activatedSignal,
|
|
6898
7130
|
pendingAt: currentTime,
|
|
6899
7131
|
_isScheduled: false,
|
|
7132
|
+
_peak: { price: activatedSignal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
6900
7133
|
};
|
|
6901
7134
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(currentTime, currentPrice, pendingSignal, this);
|
|
6902
7135
|
if (!syncOpenAllowed) {
|
|
@@ -7019,7 +7252,7 @@ class ClientStrategy {
|
|
|
7019
7252
|
if (closedResult) {
|
|
7020
7253
|
return closedResult;
|
|
7021
7254
|
}
|
|
7022
|
-
return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice);
|
|
7255
|
+
return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice, false);
|
|
7023
7256
|
}
|
|
7024
7257
|
/**
|
|
7025
7258
|
* Fast backtests a signal using historical candle data.
|
|
@@ -9006,7 +9239,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
9006
9239
|
* @param backtest - Whether running in backtest mode
|
|
9007
9240
|
* @returns Unique string key for memoization
|
|
9008
9241
|
*/
|
|
9009
|
-
const CREATE_KEY_FN$
|
|
9242
|
+
const CREATE_KEY_FN$p = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9010
9243
|
const parts = [symbol, strategyName, exchangeName];
|
|
9011
9244
|
if (frameName)
|
|
9012
9245
|
parts.push(frameName);
|
|
@@ -9130,6 +9363,43 @@ const CREATE_COMMIT_FN = (self) => trycatch(async (event) => {
|
|
|
9130
9363
|
},
|
|
9131
9364
|
defaultValue: null,
|
|
9132
9365
|
});
|
|
9366
|
+
/**
|
|
9367
|
+
* Creates a callback function for emitting highest profit updates to highestProfitSubject.
|
|
9368
|
+
* Called by ClientStrategy when the highest profit for an open position is updated.
|
|
9369
|
+
* Emits HighestProfitContract event to all subscribers with the current price and timestamp.
|
|
9370
|
+
* Used for real-time profit tracking and management logic based on profit levels.
|
|
9371
|
+
*
|
|
9372
|
+
* @param self - Reference to StrategyConnectionService instance
|
|
9373
|
+
* @param strategyName - Name of the strategy
|
|
9374
|
+
* @param exchangeName - Name of the exchange
|
|
9375
|
+
* @param frameName - Name of the frame
|
|
9376
|
+
* @param isBacktest - Flag indicating if the operation is for backtesting
|
|
9377
|
+
* @returns Callback function for highest profit updates
|
|
9378
|
+
*/
|
|
9379
|
+
const CREATE_HIGHEST_PROFIT_FN = (self, strategyName, exchangeName, frameName, isBacktest) => trycatch(async (signal, currentPrice, timestamp) => {
|
|
9380
|
+
await highestProfitSubject.next({
|
|
9381
|
+
symbol: signal.symbol,
|
|
9382
|
+
signal,
|
|
9383
|
+
currentPrice,
|
|
9384
|
+
timestamp,
|
|
9385
|
+
strategyName,
|
|
9386
|
+
exchangeName,
|
|
9387
|
+
frameName,
|
|
9388
|
+
backtest: isBacktest,
|
|
9389
|
+
});
|
|
9390
|
+
}, {
|
|
9391
|
+
fallback: (error) => {
|
|
9392
|
+
const message = "StrategyConnectionService CREATE_HIGHEST_PROFIT_FN thrown";
|
|
9393
|
+
const payload = {
|
|
9394
|
+
error: errorData(error),
|
|
9395
|
+
message: getErrorMessage(error),
|
|
9396
|
+
};
|
|
9397
|
+
bt.loggerService.warn(message, payload);
|
|
9398
|
+
console.warn(message, payload);
|
|
9399
|
+
errorEmitter.next(error);
|
|
9400
|
+
},
|
|
9401
|
+
defaultValue: null,
|
|
9402
|
+
});
|
|
9133
9403
|
/**
|
|
9134
9404
|
* Creates a callback function for emitting dispose events.
|
|
9135
9405
|
*
|
|
@@ -9200,7 +9470,7 @@ class StrategyConnectionService {
|
|
|
9200
9470
|
* @param backtest - Whether running in backtest mode
|
|
9201
9471
|
* @returns Configured ClientStrategy instance
|
|
9202
9472
|
*/
|
|
9203
|
-
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
9473
|
+
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$p(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9204
9474
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
9205
9475
|
return new ClientStrategy({
|
|
9206
9476
|
symbol,
|
|
@@ -9228,6 +9498,7 @@ class StrategyConnectionService {
|
|
|
9228
9498
|
onDispose: CREATE_COMMIT_DISPOSE_FN(this),
|
|
9229
9499
|
onCommit: CREATE_COMMIT_FN(),
|
|
9230
9500
|
onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
9501
|
+
onHighestProfit: CREATE_HIGHEST_PROFIT_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
9231
9502
|
});
|
|
9232
9503
|
});
|
|
9233
9504
|
/**
|
|
@@ -9301,14 +9572,14 @@ class StrategyConnectionService {
|
|
|
9301
9572
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9302
9573
|
* @returns Promise resolving to effective entry price or null
|
|
9303
9574
|
*/
|
|
9304
|
-
this.
|
|
9305
|
-
this.loggerService.log("strategyConnectionService
|
|
9575
|
+
this.getPositionEffectivePrice = async (backtest, symbol, context) => {
|
|
9576
|
+
this.loggerService.log("strategyConnectionService getPositionEffectivePrice", {
|
|
9306
9577
|
symbol,
|
|
9307
9578
|
context,
|
|
9308
9579
|
backtest,
|
|
9309
9580
|
});
|
|
9310
9581
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9311
|
-
return await strategy.
|
|
9582
|
+
return await strategy.getPositionEffectivePrice(symbol);
|
|
9312
9583
|
};
|
|
9313
9584
|
/**
|
|
9314
9585
|
* Returns the number of DCA entries made for the current pending signal.
|
|
@@ -9658,6 +9929,162 @@ class StrategyConnectionService {
|
|
|
9658
9929
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9659
9930
|
return await strategy.hasPendingSignal(symbol);
|
|
9660
9931
|
};
|
|
9932
|
+
/**
|
|
9933
|
+
* Returns the original estimated duration for the current pending signal.
|
|
9934
|
+
*
|
|
9935
|
+
* Delegates to ClientStrategy.getPositionEstimateMinutes().
|
|
9936
|
+
* Returns null if no pending signal exists.
|
|
9937
|
+
*
|
|
9938
|
+
* @param backtest - Whether running in backtest mode
|
|
9939
|
+
* @param symbol - Trading pair symbol
|
|
9940
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9941
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
9942
|
+
*/
|
|
9943
|
+
this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
|
|
9944
|
+
this.loggerService.log("strategyConnectionService getPositionEstimateMinutes", {
|
|
9945
|
+
symbol,
|
|
9946
|
+
context,
|
|
9947
|
+
});
|
|
9948
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9949
|
+
return await strategy.getPositionEstimateMinutes(symbol);
|
|
9950
|
+
};
|
|
9951
|
+
/**
|
|
9952
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
9953
|
+
*
|
|
9954
|
+
* Resolves current timestamp via timeMetaService and delegates to
|
|
9955
|
+
* ClientStrategy.getPositionCountdownMinutes().
|
|
9956
|
+
* Returns null if no pending signal exists.
|
|
9957
|
+
*
|
|
9958
|
+
* @param backtest - Whether running in backtest mode
|
|
9959
|
+
* @param symbol - Trading pair symbol
|
|
9960
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9961
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
9962
|
+
*/
|
|
9963
|
+
this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
|
|
9964
|
+
this.loggerService.log("strategyConnectionService getPositionCountdownMinutes", {
|
|
9965
|
+
symbol,
|
|
9966
|
+
context,
|
|
9967
|
+
});
|
|
9968
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9969
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9970
|
+
return await strategy.getPositionCountdownMinutes(symbol, timestamp);
|
|
9971
|
+
};
|
|
9972
|
+
/**
|
|
9973
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
9974
|
+
*
|
|
9975
|
+
* Delegates to ClientStrategy.getPositionHighestProfitPrice().
|
|
9976
|
+
* Returns null if no pending signal exists.
|
|
9977
|
+
*
|
|
9978
|
+
* @param backtest - Whether running in backtest mode
|
|
9979
|
+
* @param symbol - Trading pair symbol
|
|
9980
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9981
|
+
* @returns Promise resolving to price or null
|
|
9982
|
+
*/
|
|
9983
|
+
this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
|
|
9984
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitPrice", {
|
|
9985
|
+
symbol,
|
|
9986
|
+
context,
|
|
9987
|
+
});
|
|
9988
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9989
|
+
return await strategy.getPositionHighestProfitPrice(symbol);
|
|
9990
|
+
};
|
|
9991
|
+
/**
|
|
9992
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
9993
|
+
*
|
|
9994
|
+
* Delegates to ClientStrategy.getPositionHighestProfitTimestamp().
|
|
9995
|
+
* Returns null if no pending signal exists.
|
|
9996
|
+
*
|
|
9997
|
+
* @param backtest - Whether running in backtest mode
|
|
9998
|
+
* @param symbol - Trading pair symbol
|
|
9999
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10000
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
10001
|
+
*/
|
|
10002
|
+
this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
|
|
10003
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitTimestamp", {
|
|
10004
|
+
symbol,
|
|
10005
|
+
context,
|
|
10006
|
+
});
|
|
10007
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10008
|
+
return await strategy.getPositionHighestProfitTimestamp(symbol);
|
|
10009
|
+
};
|
|
10010
|
+
/**
|
|
10011
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
10012
|
+
*
|
|
10013
|
+
* Delegates to ClientStrategy.getPositionHighestPnlPercentage().
|
|
10014
|
+
* Returns null if no pending signal exists.
|
|
10015
|
+
*
|
|
10016
|
+
* @param backtest - Whether running in backtest mode
|
|
10017
|
+
* @param symbol - Trading pair symbol
|
|
10018
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10019
|
+
* @returns Promise resolving to PnL percentage or null
|
|
10020
|
+
*/
|
|
10021
|
+
this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
|
|
10022
|
+
this.loggerService.log("strategyConnectionService getPositionHighestPnlPercentage", {
|
|
10023
|
+
symbol,
|
|
10024
|
+
context,
|
|
10025
|
+
});
|
|
10026
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10027
|
+
return await strategy.getPositionHighestPnlPercentage(symbol);
|
|
10028
|
+
};
|
|
10029
|
+
/**
|
|
10030
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
10031
|
+
*
|
|
10032
|
+
* Delegates to ClientStrategy.getPositionHighestPnlCost().
|
|
10033
|
+
* Returns null if no pending signal exists.
|
|
10034
|
+
*
|
|
10035
|
+
* @param backtest - Whether running in backtest mode
|
|
10036
|
+
* @param symbol - Trading pair symbol
|
|
10037
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10038
|
+
* @returns Promise resolving to PnL cost or null
|
|
10039
|
+
*/
|
|
10040
|
+
this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
|
|
10041
|
+
this.loggerService.log("strategyConnectionService getPositionHighestPnlCost", {
|
|
10042
|
+
symbol,
|
|
10043
|
+
context,
|
|
10044
|
+
});
|
|
10045
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10046
|
+
return await strategy.getPositionHighestPnlCost(symbol);
|
|
10047
|
+
};
|
|
10048
|
+
/**
|
|
10049
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
10050
|
+
*
|
|
10051
|
+
* Delegates to ClientStrategy.getPositionHighestProfitBreakeven().
|
|
10052
|
+
* Returns null if no pending signal exists.
|
|
10053
|
+
*
|
|
10054
|
+
* @param backtest - Whether running in backtest mode
|
|
10055
|
+
* @param symbol - Trading pair symbol
|
|
10056
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10057
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
10058
|
+
*/
|
|
10059
|
+
this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
|
|
10060
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitBreakeven", {
|
|
10061
|
+
symbol,
|
|
10062
|
+
context,
|
|
10063
|
+
});
|
|
10064
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10065
|
+
return await strategy.getPositionHighestProfitBreakeven(symbol);
|
|
10066
|
+
};
|
|
10067
|
+
/**
|
|
10068
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
10069
|
+
*
|
|
10070
|
+
* Resolves current timestamp via timeMetaService and delegates to
|
|
10071
|
+
* ClientStrategy.getPositionDrawdownMinutes().
|
|
10072
|
+
* Returns null if no pending signal exists.
|
|
10073
|
+
*
|
|
10074
|
+
* @param backtest - Whether running in backtest mode
|
|
10075
|
+
* @param symbol - Trading pair symbol
|
|
10076
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10077
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
10078
|
+
*/
|
|
10079
|
+
this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
|
|
10080
|
+
this.loggerService.log("strategyConnectionService getPositionDrawdownMinutes", {
|
|
10081
|
+
symbol,
|
|
10082
|
+
context,
|
|
10083
|
+
});
|
|
10084
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10085
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
10086
|
+
return await strategy.getPositionDrawdownMinutes(symbol, timestamp);
|
|
10087
|
+
};
|
|
9661
10088
|
/**
|
|
9662
10089
|
* Disposes the ClientStrategy instance for the given context.
|
|
9663
10090
|
*
|
|
@@ -9696,7 +10123,7 @@ class StrategyConnectionService {
|
|
|
9696
10123
|
}
|
|
9697
10124
|
return;
|
|
9698
10125
|
}
|
|
9699
|
-
const key = CREATE_KEY_FN$
|
|
10126
|
+
const key = CREATE_KEY_FN$p(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
9700
10127
|
if (!this.getStrategy.has(key)) {
|
|
9701
10128
|
return;
|
|
9702
10129
|
}
|
|
@@ -10869,7 +11296,7 @@ class ClientRisk {
|
|
|
10869
11296
|
* @param backtest - Whether running in backtest mode
|
|
10870
11297
|
* @returns Unique string key for memoization
|
|
10871
11298
|
*/
|
|
10872
|
-
const CREATE_KEY_FN$
|
|
11299
|
+
const CREATE_KEY_FN$o = (riskName, exchangeName, frameName, backtest) => {
|
|
10873
11300
|
const parts = [riskName, exchangeName];
|
|
10874
11301
|
if (frameName)
|
|
10875
11302
|
parts.push(frameName);
|
|
@@ -10968,7 +11395,7 @@ class RiskConnectionService {
|
|
|
10968
11395
|
* @param backtest - True if backtest mode, false if live mode
|
|
10969
11396
|
* @returns Configured ClientRisk instance
|
|
10970
11397
|
*/
|
|
10971
|
-
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
11398
|
+
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
10972
11399
|
const schema = this.riskSchemaService.get(riskName);
|
|
10973
11400
|
return new ClientRisk({
|
|
10974
11401
|
...schema,
|
|
@@ -11036,7 +11463,7 @@ class RiskConnectionService {
|
|
|
11036
11463
|
payload,
|
|
11037
11464
|
});
|
|
11038
11465
|
if (payload) {
|
|
11039
|
-
const key = CREATE_KEY_FN$
|
|
11466
|
+
const key = CREATE_KEY_FN$o(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
11040
11467
|
this.getRisk.clear(key);
|
|
11041
11468
|
}
|
|
11042
11469
|
else {
|
|
@@ -12503,7 +12930,7 @@ class ClientAction {
|
|
|
12503
12930
|
* @param backtest - Whether running in backtest mode
|
|
12504
12931
|
* @returns Unique string key for memoization
|
|
12505
12932
|
*/
|
|
12506
|
-
const CREATE_KEY_FN$
|
|
12933
|
+
const CREATE_KEY_FN$n = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12507
12934
|
const parts = [actionName, strategyName, exchangeName];
|
|
12508
12935
|
if (frameName)
|
|
12509
12936
|
parts.push(frameName);
|
|
@@ -12554,7 +12981,7 @@ class ActionConnectionService {
|
|
|
12554
12981
|
* @param backtest - True if backtest mode, false if live mode
|
|
12555
12982
|
* @returns Configured ClientAction instance
|
|
12556
12983
|
*/
|
|
12557
|
-
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12984
|
+
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12558
12985
|
const schema = this.actionSchemaService.get(actionName);
|
|
12559
12986
|
return new ClientAction({
|
|
12560
12987
|
...schema,
|
|
@@ -12764,7 +13191,7 @@ class ActionConnectionService {
|
|
|
12764
13191
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
12765
13192
|
return;
|
|
12766
13193
|
}
|
|
12767
|
-
const key = CREATE_KEY_FN$
|
|
13194
|
+
const key = CREATE_KEY_FN$n(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12768
13195
|
if (!this.getAction.has(key)) {
|
|
12769
13196
|
return;
|
|
12770
13197
|
}
|
|
@@ -12782,7 +13209,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
12782
13209
|
* @param exchangeName - Exchange name
|
|
12783
13210
|
* @returns Unique string key for memoization
|
|
12784
13211
|
*/
|
|
12785
|
-
const CREATE_KEY_FN$
|
|
13212
|
+
const CREATE_KEY_FN$m = (exchangeName) => {
|
|
12786
13213
|
return exchangeName;
|
|
12787
13214
|
};
|
|
12788
13215
|
/**
|
|
@@ -12806,7 +13233,7 @@ class ExchangeCoreService {
|
|
|
12806
13233
|
* @param exchangeName - Name of the exchange to validate
|
|
12807
13234
|
* @returns Promise that resolves when validation is complete
|
|
12808
13235
|
*/
|
|
12809
|
-
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
13236
|
+
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$m(exchangeName), async (exchangeName) => {
|
|
12810
13237
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
12811
13238
|
exchangeName,
|
|
12812
13239
|
});
|
|
@@ -13058,7 +13485,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
13058
13485
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13059
13486
|
* @returns Unique string key for memoization
|
|
13060
13487
|
*/
|
|
13061
|
-
const CREATE_KEY_FN$
|
|
13488
|
+
const CREATE_KEY_FN$l = (context) => {
|
|
13062
13489
|
const parts = [context.strategyName, context.exchangeName];
|
|
13063
13490
|
if (context.frameName)
|
|
13064
13491
|
parts.push(context.frameName);
|
|
@@ -13090,7 +13517,7 @@ class StrategyCoreService {
|
|
|
13090
13517
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13091
13518
|
* @returns Promise that resolves when validation is complete
|
|
13092
13519
|
*/
|
|
13093
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
13520
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$l(context), async (context) => {
|
|
13094
13521
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
13095
13522
|
context,
|
|
13096
13523
|
});
|
|
@@ -13154,13 +13581,13 @@ class StrategyCoreService {
|
|
|
13154
13581
|
await this.validate(context);
|
|
13155
13582
|
return await this.strategyConnectionService.getTotalCostClosed(backtest, symbol, context);
|
|
13156
13583
|
};
|
|
13157
|
-
this.
|
|
13158
|
-
this.loggerService.log("strategyCoreService
|
|
13584
|
+
this.getPositionEffectivePrice = async (backtest, symbol, context) => {
|
|
13585
|
+
this.loggerService.log("strategyCoreService getPositionEffectivePrice", {
|
|
13159
13586
|
symbol,
|
|
13160
13587
|
context,
|
|
13161
13588
|
});
|
|
13162
13589
|
await this.validate(context);
|
|
13163
|
-
return await this.strategyConnectionService.
|
|
13590
|
+
return await this.strategyConnectionService.getPositionEffectivePrice(backtest, symbol, context);
|
|
13164
13591
|
};
|
|
13165
13592
|
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
13166
13593
|
this.loggerService.log("strategyCoreService getPositionInvestedCount", {
|
|
@@ -13853,6 +14280,143 @@ class StrategyCoreService {
|
|
|
13853
14280
|
await this.validate(context);
|
|
13854
14281
|
return await this.strategyConnectionService.hasPendingSignal(backtest, symbol, context);
|
|
13855
14282
|
};
|
|
14283
|
+
/**
|
|
14284
|
+
* Returns the original estimated duration for the current pending signal.
|
|
14285
|
+
*
|
|
14286
|
+
* Validates strategy existence and delegates to connection service.
|
|
14287
|
+
* Returns null if no pending signal exists.
|
|
14288
|
+
*
|
|
14289
|
+
* @param backtest - Whether running in backtest mode
|
|
14290
|
+
* @param symbol - Trading pair symbol
|
|
14291
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14292
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
14293
|
+
*/
|
|
14294
|
+
this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
|
|
14295
|
+
this.loggerService.log("strategyCoreService getPositionEstimateMinutes", {
|
|
14296
|
+
symbol,
|
|
14297
|
+
context,
|
|
14298
|
+
});
|
|
14299
|
+
await this.validate(context);
|
|
14300
|
+
return await this.strategyConnectionService.getPositionEstimateMinutes(backtest, symbol, context);
|
|
14301
|
+
};
|
|
14302
|
+
/**
|
|
14303
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
14304
|
+
*
|
|
14305
|
+
* Validates strategy existence and delegates to connection service.
|
|
14306
|
+
* Returns null if no pending signal exists.
|
|
14307
|
+
*
|
|
14308
|
+
* @param backtest - Whether running in backtest mode
|
|
14309
|
+
* @param symbol - Trading pair symbol
|
|
14310
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14311
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
14312
|
+
*/
|
|
14313
|
+
this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
|
|
14314
|
+
this.loggerService.log("strategyCoreService getPositionCountdownMinutes", {
|
|
14315
|
+
symbol,
|
|
14316
|
+
context,
|
|
14317
|
+
});
|
|
14318
|
+
await this.validate(context);
|
|
14319
|
+
return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
|
|
14320
|
+
};
|
|
14321
|
+
/**
|
|
14322
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
14323
|
+
*
|
|
14324
|
+
* @param backtest - Whether running in backtest mode
|
|
14325
|
+
* @param symbol - Trading pair symbol
|
|
14326
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14327
|
+
* @returns Promise resolving to price or null
|
|
14328
|
+
*/
|
|
14329
|
+
this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
|
|
14330
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitPrice", {
|
|
14331
|
+
symbol,
|
|
14332
|
+
context,
|
|
14333
|
+
});
|
|
14334
|
+
await this.validate(context);
|
|
14335
|
+
return await this.strategyConnectionService.getPositionHighestProfitPrice(backtest, symbol, context);
|
|
14336
|
+
};
|
|
14337
|
+
/**
|
|
14338
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
14339
|
+
*
|
|
14340
|
+
* @param backtest - Whether running in backtest mode
|
|
14341
|
+
* @param symbol - Trading pair symbol
|
|
14342
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14343
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
14344
|
+
*/
|
|
14345
|
+
this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
|
|
14346
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitTimestamp", {
|
|
14347
|
+
symbol,
|
|
14348
|
+
context,
|
|
14349
|
+
});
|
|
14350
|
+
await this.validate(context);
|
|
14351
|
+
return await this.strategyConnectionService.getPositionHighestProfitTimestamp(backtest, symbol, context);
|
|
14352
|
+
};
|
|
14353
|
+
/**
|
|
14354
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
14355
|
+
*
|
|
14356
|
+
* @param backtest - Whether running in backtest mode
|
|
14357
|
+
* @param symbol - Trading pair symbol
|
|
14358
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14359
|
+
* @returns Promise resolving to PnL percentage or null
|
|
14360
|
+
*/
|
|
14361
|
+
this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
|
|
14362
|
+
this.loggerService.log("strategyCoreService getPositionHighestPnlPercentage", {
|
|
14363
|
+
symbol,
|
|
14364
|
+
context,
|
|
14365
|
+
});
|
|
14366
|
+
await this.validate(context);
|
|
14367
|
+
return await this.strategyConnectionService.getPositionHighestPnlPercentage(backtest, symbol, context);
|
|
14368
|
+
};
|
|
14369
|
+
/**
|
|
14370
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
14371
|
+
*
|
|
14372
|
+
* @param backtest - Whether running in backtest mode
|
|
14373
|
+
* @param symbol - Trading pair symbol
|
|
14374
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14375
|
+
* @returns Promise resolving to PnL cost or null
|
|
14376
|
+
*/
|
|
14377
|
+
this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
|
|
14378
|
+
this.loggerService.log("strategyCoreService getPositionHighestPnlCost", {
|
|
14379
|
+
symbol,
|
|
14380
|
+
context,
|
|
14381
|
+
});
|
|
14382
|
+
await this.validate(context);
|
|
14383
|
+
return await this.strategyConnectionService.getPositionHighestPnlCost(backtest, symbol, context);
|
|
14384
|
+
};
|
|
14385
|
+
/**
|
|
14386
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
14387
|
+
*
|
|
14388
|
+
* @param backtest - Whether running in backtest mode
|
|
14389
|
+
* @param symbol - Trading pair symbol
|
|
14390
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14391
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
14392
|
+
*/
|
|
14393
|
+
this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
|
|
14394
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitBreakeven", {
|
|
14395
|
+
symbol,
|
|
14396
|
+
context,
|
|
14397
|
+
});
|
|
14398
|
+
await this.validate(context);
|
|
14399
|
+
return await this.strategyConnectionService.getPositionHighestProfitBreakeven(backtest, symbol, context);
|
|
14400
|
+
};
|
|
14401
|
+
/**
|
|
14402
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
14403
|
+
*
|
|
14404
|
+
* Validates strategy existence and delegates to connection service.
|
|
14405
|
+
* Returns null if no pending signal exists.
|
|
14406
|
+
*
|
|
14407
|
+
* @param backtest - Whether running in backtest mode
|
|
14408
|
+
* @param symbol - Trading pair symbol
|
|
14409
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14410
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
14411
|
+
*/
|
|
14412
|
+
this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
|
|
14413
|
+
this.loggerService.log("strategyCoreService getPositionDrawdownMinutes", {
|
|
14414
|
+
symbol,
|
|
14415
|
+
context,
|
|
14416
|
+
});
|
|
14417
|
+
await this.validate(context);
|
|
14418
|
+
return await this.strategyConnectionService.getPositionDrawdownMinutes(backtest, symbol, context);
|
|
14419
|
+
};
|
|
13856
14420
|
}
|
|
13857
14421
|
}
|
|
13858
14422
|
|
|
@@ -13925,7 +14489,7 @@ class SizingGlobalService {
|
|
|
13925
14489
|
* @param context - Context with riskName, exchangeName, frameName
|
|
13926
14490
|
* @returns Unique string key for memoization
|
|
13927
14491
|
*/
|
|
13928
|
-
const CREATE_KEY_FN$
|
|
14492
|
+
const CREATE_KEY_FN$k = (context) => {
|
|
13929
14493
|
const parts = [context.riskName, context.exchangeName];
|
|
13930
14494
|
if (context.frameName)
|
|
13931
14495
|
parts.push(context.frameName);
|
|
@@ -13951,7 +14515,7 @@ class RiskGlobalService {
|
|
|
13951
14515
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
13952
14516
|
* @returns Promise that resolves when validation is complete
|
|
13953
14517
|
*/
|
|
13954
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
14518
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
|
|
13955
14519
|
this.loggerService.log("riskGlobalService validate", {
|
|
13956
14520
|
context,
|
|
13957
14521
|
});
|
|
@@ -14029,7 +14593,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
14029
14593
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14030
14594
|
* @returns Unique string key for memoization
|
|
14031
14595
|
*/
|
|
14032
|
-
const CREATE_KEY_FN$
|
|
14596
|
+
const CREATE_KEY_FN$j = (context) => {
|
|
14033
14597
|
const parts = [context.strategyName, context.exchangeName];
|
|
14034
14598
|
if (context.frameName)
|
|
14035
14599
|
parts.push(context.frameName);
|
|
@@ -14073,7 +14637,7 @@ class ActionCoreService {
|
|
|
14073
14637
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
14074
14638
|
* @returns Promise that resolves when all validations complete
|
|
14075
14639
|
*/
|
|
14076
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
14640
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
|
|
14077
14641
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
14078
14642
|
context,
|
|
14079
14643
|
});
|
|
@@ -17884,6 +18448,78 @@ const sync_columns = [
|
|
|
17884
18448
|
},
|
|
17885
18449
|
];
|
|
17886
18450
|
|
|
18451
|
+
/**
|
|
18452
|
+
* Column configuration for highest profit markdown reports.
|
|
18453
|
+
*
|
|
18454
|
+
* Defines the table structure for displaying highest-profit-record events.
|
|
18455
|
+
*
|
|
18456
|
+
* @see HighestProfitMarkdownService
|
|
18457
|
+
* @see ColumnModel
|
|
18458
|
+
* @see HighestProfitEvent
|
|
18459
|
+
*/
|
|
18460
|
+
const highest_profit_columns = [
|
|
18461
|
+
{
|
|
18462
|
+
key: "symbol",
|
|
18463
|
+
label: "Symbol",
|
|
18464
|
+
format: (data) => data.symbol,
|
|
18465
|
+
isVisible: () => true,
|
|
18466
|
+
},
|
|
18467
|
+
{
|
|
18468
|
+
key: "strategyName",
|
|
18469
|
+
label: "Strategy",
|
|
18470
|
+
format: (data) => data.strategyName,
|
|
18471
|
+
isVisible: () => true,
|
|
18472
|
+
},
|
|
18473
|
+
{
|
|
18474
|
+
key: "signalId",
|
|
18475
|
+
label: "Signal ID",
|
|
18476
|
+
format: (data) => data.signalId,
|
|
18477
|
+
isVisible: () => true,
|
|
18478
|
+
},
|
|
18479
|
+
{
|
|
18480
|
+
key: "position",
|
|
18481
|
+
label: "Position",
|
|
18482
|
+
format: (data) => data.position.toUpperCase(),
|
|
18483
|
+
isVisible: () => true,
|
|
18484
|
+
},
|
|
18485
|
+
{
|
|
18486
|
+
key: "currentPrice",
|
|
18487
|
+
label: "Record Price",
|
|
18488
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
18489
|
+
isVisible: () => true,
|
|
18490
|
+
},
|
|
18491
|
+
{
|
|
18492
|
+
key: "priceOpen",
|
|
18493
|
+
label: "Entry Price",
|
|
18494
|
+
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
18495
|
+
isVisible: () => true,
|
|
18496
|
+
},
|
|
18497
|
+
{
|
|
18498
|
+
key: "priceTakeProfit",
|
|
18499
|
+
label: "Take Profit",
|
|
18500
|
+
format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
|
|
18501
|
+
isVisible: () => true,
|
|
18502
|
+
},
|
|
18503
|
+
{
|
|
18504
|
+
key: "priceStopLoss",
|
|
18505
|
+
label: "Stop Loss",
|
|
18506
|
+
format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
|
|
18507
|
+
isVisible: () => true,
|
|
18508
|
+
},
|
|
18509
|
+
{
|
|
18510
|
+
key: "timestamp",
|
|
18511
|
+
label: "Timestamp",
|
|
18512
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
18513
|
+
isVisible: () => true,
|
|
18514
|
+
},
|
|
18515
|
+
{
|
|
18516
|
+
key: "mode",
|
|
18517
|
+
label: "Mode",
|
|
18518
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
18519
|
+
isVisible: () => true,
|
|
18520
|
+
},
|
|
18521
|
+
];
|
|
18522
|
+
|
|
17887
18523
|
/**
|
|
17888
18524
|
* Column configuration for walker strategy comparison table in markdown reports.
|
|
17889
18525
|
*
|
|
@@ -18101,6 +18737,8 @@ const COLUMN_CONFIG = {
|
|
|
18101
18737
|
strategy_columns,
|
|
18102
18738
|
/** Columns for signal sync lifecycle events (signal-open, signal-close) */
|
|
18103
18739
|
sync_columns,
|
|
18740
|
+
/** Columns for highest profit milestone tracking events */
|
|
18741
|
+
highest_profit_columns,
|
|
18104
18742
|
/** Walker: PnL summary columns */
|
|
18105
18743
|
walker_pnl_columns,
|
|
18106
18744
|
/** Walker: strategy-level summary columns */
|
|
@@ -18141,6 +18779,7 @@ const WILDCARD_TARGET$1 = {
|
|
|
18141
18779
|
schedule: true,
|
|
18142
18780
|
walker: true,
|
|
18143
18781
|
sync: true,
|
|
18782
|
+
highest_profit: true,
|
|
18144
18783
|
};
|
|
18145
18784
|
/**
|
|
18146
18785
|
* JSONL-based markdown adapter with append-only writes.
|
|
@@ -18368,7 +19007,7 @@ class MarkdownUtils {
|
|
|
18368
19007
|
*
|
|
18369
19008
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
18370
19009
|
*/
|
|
18371
|
-
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, } = WILDCARD_TARGET$1) => {
|
|
19010
|
+
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, highest_profit = false, } = WILDCARD_TARGET$1) => {
|
|
18372
19011
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
|
|
18373
19012
|
backtest: bt$1,
|
|
18374
19013
|
breakeven,
|
|
@@ -18381,6 +19020,7 @@ class MarkdownUtils {
|
|
|
18381
19020
|
schedule,
|
|
18382
19021
|
walker,
|
|
18383
19022
|
sync,
|
|
19023
|
+
highest_profit,
|
|
18384
19024
|
});
|
|
18385
19025
|
const unList = [];
|
|
18386
19026
|
if (bt$1) {
|
|
@@ -18416,6 +19056,9 @@ class MarkdownUtils {
|
|
|
18416
19056
|
if (sync) {
|
|
18417
19057
|
unList.push(bt.syncMarkdownService.subscribe());
|
|
18418
19058
|
}
|
|
19059
|
+
if (highest_profit) {
|
|
19060
|
+
unList.push(bt.highestProfitMarkdownService.subscribe());
|
|
19061
|
+
}
|
|
18419
19062
|
return compose(...unList.map((un) => () => void un()));
|
|
18420
19063
|
};
|
|
18421
19064
|
/**
|
|
@@ -18455,7 +19098,7 @@ class MarkdownUtils {
|
|
|
18455
19098
|
* Markdown.disable();
|
|
18456
19099
|
* ```
|
|
18457
19100
|
*/
|
|
18458
|
-
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, } = WILDCARD_TARGET$1) => {
|
|
19101
|
+
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, } = WILDCARD_TARGET$1) => {
|
|
18459
19102
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
|
|
18460
19103
|
backtest: bt$1,
|
|
18461
19104
|
breakeven,
|
|
@@ -18468,6 +19111,7 @@ class MarkdownUtils {
|
|
|
18468
19111
|
schedule,
|
|
18469
19112
|
walker,
|
|
18470
19113
|
sync,
|
|
19114
|
+
highest_profit,
|
|
18471
19115
|
});
|
|
18472
19116
|
if (bt$1) {
|
|
18473
19117
|
bt.backtestMarkdownService.unsubscribe();
|
|
@@ -18502,6 +19146,9 @@ class MarkdownUtils {
|
|
|
18502
19146
|
if (sync) {
|
|
18503
19147
|
bt.syncMarkdownService.unsubscribe();
|
|
18504
19148
|
}
|
|
19149
|
+
if (highest_profit) {
|
|
19150
|
+
bt.highestProfitMarkdownService.unsubscribe();
|
|
19151
|
+
}
|
|
18505
19152
|
};
|
|
18506
19153
|
}
|
|
18507
19154
|
}
|
|
@@ -18608,7 +19255,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
18608
19255
|
* @param backtest - Whether running in backtest mode
|
|
18609
19256
|
* @returns Unique string key for memoization
|
|
18610
19257
|
*/
|
|
18611
|
-
const CREATE_KEY_FN$
|
|
19258
|
+
const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
18612
19259
|
const parts = [symbol, strategyName, exchangeName];
|
|
18613
19260
|
if (frameName)
|
|
18614
19261
|
parts.push(frameName);
|
|
@@ -18625,7 +19272,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
18625
19272
|
* @param timestamp - Unix timestamp in milliseconds
|
|
18626
19273
|
* @returns Filename string
|
|
18627
19274
|
*/
|
|
18628
|
-
const CREATE_FILE_NAME_FN$
|
|
19275
|
+
const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
18629
19276
|
const parts = [symbol, strategyName, exchangeName];
|
|
18630
19277
|
if (frameName) {
|
|
18631
19278
|
parts.push(frameName);
|
|
@@ -18654,12 +19301,12 @@ function isUnsafe$3(value) {
|
|
|
18654
19301
|
return false;
|
|
18655
19302
|
}
|
|
18656
19303
|
/** Maximum number of signals to store in backtest reports */
|
|
18657
|
-
const MAX_EVENTS$
|
|
19304
|
+
const MAX_EVENTS$a = 250;
|
|
18658
19305
|
/**
|
|
18659
19306
|
* Storage class for accumulating closed signals per strategy.
|
|
18660
19307
|
* Maintains a list of all closed signals and provides methods to generate reports.
|
|
18661
19308
|
*/
|
|
18662
|
-
let ReportStorage$
|
|
19309
|
+
let ReportStorage$9 = class ReportStorage {
|
|
18663
19310
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
18664
19311
|
this.symbol = symbol;
|
|
18665
19312
|
this.strategyName = strategyName;
|
|
@@ -18676,7 +19323,7 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
18676
19323
|
addSignal(data) {
|
|
18677
19324
|
this._signalList.unshift(data);
|
|
18678
19325
|
// Trim queue if exceeded MAX_EVENTS
|
|
18679
|
-
if (this._signalList.length > MAX_EVENTS$
|
|
19326
|
+
if (this._signalList.length > MAX_EVENTS$a) {
|
|
18680
19327
|
this._signalList.pop();
|
|
18681
19328
|
}
|
|
18682
19329
|
}
|
|
@@ -18800,7 +19447,7 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
18800
19447
|
async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
18801
19448
|
const markdown = await this.getReport(strategyName, columns);
|
|
18802
19449
|
const timestamp = getContextTimestamp();
|
|
18803
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
19450
|
+
const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
18804
19451
|
await Markdown.writeData("backtest", markdown, {
|
|
18805
19452
|
path,
|
|
18806
19453
|
file: filename,
|
|
@@ -18847,7 +19494,7 @@ class BacktestMarkdownService {
|
|
|
18847
19494
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
18848
19495
|
* Each combination gets its own isolated storage instance.
|
|
18849
19496
|
*/
|
|
18850
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
19497
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
|
|
18851
19498
|
/**
|
|
18852
19499
|
* Processes tick events and accumulates closed signals.
|
|
18853
19500
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -19004,7 +19651,7 @@ class BacktestMarkdownService {
|
|
|
19004
19651
|
payload,
|
|
19005
19652
|
});
|
|
19006
19653
|
if (payload) {
|
|
19007
|
-
const key = CREATE_KEY_FN$
|
|
19654
|
+
const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19008
19655
|
this.getStorage.clear(key);
|
|
19009
19656
|
}
|
|
19010
19657
|
else {
|
|
@@ -19066,7 +19713,7 @@ class BacktestMarkdownService {
|
|
|
19066
19713
|
* @param backtest - Whether running in backtest mode
|
|
19067
19714
|
* @returns Unique string key for memoization
|
|
19068
19715
|
*/
|
|
19069
|
-
const CREATE_KEY_FN$
|
|
19716
|
+
const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19070
19717
|
const parts = [symbol, strategyName, exchangeName];
|
|
19071
19718
|
if (frameName)
|
|
19072
19719
|
parts.push(frameName);
|
|
@@ -19083,7 +19730,7 @@ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19083
19730
|
* @param timestamp - Unix timestamp in milliseconds
|
|
19084
19731
|
* @returns Filename string
|
|
19085
19732
|
*/
|
|
19086
|
-
const CREATE_FILE_NAME_FN$
|
|
19733
|
+
const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19087
19734
|
const parts = [symbol, strategyName, exchangeName];
|
|
19088
19735
|
if (frameName) {
|
|
19089
19736
|
parts.push(frameName);
|
|
@@ -19112,12 +19759,12 @@ function isUnsafe$2(value) {
|
|
|
19112
19759
|
return false;
|
|
19113
19760
|
}
|
|
19114
19761
|
/** Maximum number of events to store in live trading reports */
|
|
19115
|
-
const MAX_EVENTS$
|
|
19762
|
+
const MAX_EVENTS$9 = 250;
|
|
19116
19763
|
/**
|
|
19117
19764
|
* Storage class for accumulating all tick events per strategy.
|
|
19118
19765
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
19119
19766
|
*/
|
|
19120
|
-
let ReportStorage$
|
|
19767
|
+
let ReportStorage$8 = class ReportStorage {
|
|
19121
19768
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19122
19769
|
this.symbol = symbol;
|
|
19123
19770
|
this.strategyName = strategyName;
|
|
@@ -19149,7 +19796,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19149
19796
|
}
|
|
19150
19797
|
{
|
|
19151
19798
|
this._eventList.unshift(newEvent);
|
|
19152
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19799
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19153
19800
|
this._eventList.pop();
|
|
19154
19801
|
}
|
|
19155
19802
|
}
|
|
@@ -19179,7 +19826,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19179
19826
|
scheduledAt: data.signal.scheduledAt,
|
|
19180
19827
|
});
|
|
19181
19828
|
// Trim queue if exceeded MAX_EVENTS
|
|
19182
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19829
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19183
19830
|
this._eventList.pop();
|
|
19184
19831
|
}
|
|
19185
19832
|
}
|
|
@@ -19223,7 +19870,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19223
19870
|
// If no previous active event found, add new event
|
|
19224
19871
|
this._eventList.unshift(newEvent);
|
|
19225
19872
|
// Trim queue if exceeded MAX_EVENTS
|
|
19226
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19873
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19227
19874
|
this._eventList.pop();
|
|
19228
19875
|
}
|
|
19229
19876
|
}
|
|
@@ -19260,7 +19907,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19260
19907
|
};
|
|
19261
19908
|
this._eventList.unshift(newEvent);
|
|
19262
19909
|
// Trim queue if exceeded MAX_EVENTS
|
|
19263
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19910
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19264
19911
|
this._eventList.pop();
|
|
19265
19912
|
}
|
|
19266
19913
|
}
|
|
@@ -19288,7 +19935,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19288
19935
|
scheduledAt: data.signal.scheduledAt,
|
|
19289
19936
|
});
|
|
19290
19937
|
// Trim queue if exceeded MAX_EVENTS
|
|
19291
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19938
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19292
19939
|
this._eventList.pop();
|
|
19293
19940
|
}
|
|
19294
19941
|
}
|
|
@@ -19331,7 +19978,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19331
19978
|
// If no previous waiting event found, add new event
|
|
19332
19979
|
this._eventList.unshift(newEvent);
|
|
19333
19980
|
// Trim queue if exceeded MAX_EVENTS
|
|
19334
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19981
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19335
19982
|
this._eventList.pop();
|
|
19336
19983
|
}
|
|
19337
19984
|
}
|
|
@@ -19360,7 +20007,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19360
20007
|
scheduledAt: data.signal.scheduledAt,
|
|
19361
20008
|
});
|
|
19362
20009
|
// Trim queue if exceeded MAX_EVENTS
|
|
19363
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20010
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19364
20011
|
this._eventList.pop();
|
|
19365
20012
|
}
|
|
19366
20013
|
}
|
|
@@ -19499,7 +20146,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19499
20146
|
async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
19500
20147
|
const markdown = await this.getReport(strategyName, columns);
|
|
19501
20148
|
const timestamp = getContextTimestamp();
|
|
19502
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20149
|
+
const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
19503
20150
|
await Markdown.writeData("live", markdown, {
|
|
19504
20151
|
path,
|
|
19505
20152
|
signalId: "",
|
|
@@ -19549,7 +20196,7 @@ class LiveMarkdownService {
|
|
|
19549
20196
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19550
20197
|
* Each combination gets its own isolated storage instance.
|
|
19551
20198
|
*/
|
|
19552
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20199
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
19553
20200
|
/**
|
|
19554
20201
|
* Subscribes to live signal emitter to receive tick events.
|
|
19555
20202
|
* Protected against multiple subscriptions.
|
|
@@ -19767,7 +20414,7 @@ class LiveMarkdownService {
|
|
|
19767
20414
|
payload,
|
|
19768
20415
|
});
|
|
19769
20416
|
if (payload) {
|
|
19770
|
-
const key = CREATE_KEY_FN$
|
|
20417
|
+
const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19771
20418
|
this.getStorage.clear(key);
|
|
19772
20419
|
}
|
|
19773
20420
|
else {
|
|
@@ -19787,7 +20434,7 @@ class LiveMarkdownService {
|
|
|
19787
20434
|
* @param backtest - Whether running in backtest mode
|
|
19788
20435
|
* @returns Unique string key for memoization
|
|
19789
20436
|
*/
|
|
19790
|
-
const CREATE_KEY_FN$
|
|
20437
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19791
20438
|
const parts = [symbol, strategyName, exchangeName];
|
|
19792
20439
|
if (frameName)
|
|
19793
20440
|
parts.push(frameName);
|
|
@@ -19804,7 +20451,7 @@ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19804
20451
|
* @param timestamp - Unix timestamp in milliseconds
|
|
19805
20452
|
* @returns Filename string
|
|
19806
20453
|
*/
|
|
19807
|
-
const CREATE_FILE_NAME_FN$
|
|
20454
|
+
const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19808
20455
|
const parts = [symbol, strategyName, exchangeName];
|
|
19809
20456
|
if (frameName) {
|
|
19810
20457
|
parts.push(frameName);
|
|
@@ -19815,12 +20462,12 @@ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
19815
20462
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
19816
20463
|
};
|
|
19817
20464
|
/** Maximum number of events to store in schedule reports */
|
|
19818
|
-
const MAX_EVENTS$
|
|
20465
|
+
const MAX_EVENTS$8 = 250;
|
|
19819
20466
|
/**
|
|
19820
20467
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
19821
20468
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
19822
20469
|
*/
|
|
19823
|
-
let ReportStorage$
|
|
20470
|
+
let ReportStorage$7 = class ReportStorage {
|
|
19824
20471
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19825
20472
|
this.symbol = symbol;
|
|
19826
20473
|
this.strategyName = strategyName;
|
|
@@ -19856,7 +20503,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19856
20503
|
scheduledAt: data.signal.scheduledAt,
|
|
19857
20504
|
});
|
|
19858
20505
|
// Trim queue if exceeded MAX_EVENTS
|
|
19859
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20506
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19860
20507
|
this._eventList.pop();
|
|
19861
20508
|
}
|
|
19862
20509
|
}
|
|
@@ -19892,7 +20539,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19892
20539
|
};
|
|
19893
20540
|
this._eventList.unshift(newEvent);
|
|
19894
20541
|
// Trim queue if exceeded MAX_EVENTS
|
|
19895
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20542
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19896
20543
|
this._eventList.pop();
|
|
19897
20544
|
}
|
|
19898
20545
|
}
|
|
@@ -19930,7 +20577,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19930
20577
|
};
|
|
19931
20578
|
this._eventList.unshift(newEvent);
|
|
19932
20579
|
// Trim queue if exceeded MAX_EVENTS
|
|
19933
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20580
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19934
20581
|
this._eventList.pop();
|
|
19935
20582
|
}
|
|
19936
20583
|
}
|
|
@@ -20037,7 +20684,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
20037
20684
|
async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
20038
20685
|
const markdown = await this.getReport(strategyName, columns);
|
|
20039
20686
|
const timestamp = getContextTimestamp();
|
|
20040
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20687
|
+
const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20041
20688
|
await Markdown.writeData("schedule", markdown, {
|
|
20042
20689
|
path,
|
|
20043
20690
|
file: filename,
|
|
@@ -20078,7 +20725,7 @@ class ScheduleMarkdownService {
|
|
|
20078
20725
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20079
20726
|
* Each combination gets its own isolated storage instance.
|
|
20080
20727
|
*/
|
|
20081
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20728
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
|
|
20082
20729
|
/**
|
|
20083
20730
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
20084
20731
|
* Protected against multiple subscriptions.
|
|
@@ -20281,7 +20928,7 @@ class ScheduleMarkdownService {
|
|
|
20281
20928
|
payload,
|
|
20282
20929
|
});
|
|
20283
20930
|
if (payload) {
|
|
20284
|
-
const key = CREATE_KEY_FN$
|
|
20931
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20285
20932
|
this.getStorage.clear(key);
|
|
20286
20933
|
}
|
|
20287
20934
|
else {
|
|
@@ -20301,7 +20948,7 @@ class ScheduleMarkdownService {
|
|
|
20301
20948
|
* @param backtest - Whether running in backtest mode
|
|
20302
20949
|
* @returns Unique string key for memoization
|
|
20303
20950
|
*/
|
|
20304
|
-
const CREATE_KEY_FN$
|
|
20951
|
+
const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20305
20952
|
const parts = [symbol, strategyName, exchangeName];
|
|
20306
20953
|
if (frameName)
|
|
20307
20954
|
parts.push(frameName);
|
|
@@ -20318,7 +20965,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20318
20965
|
* @param timestamp - Unix timestamp in milliseconds
|
|
20319
20966
|
* @returns Filename string
|
|
20320
20967
|
*/
|
|
20321
|
-
const CREATE_FILE_NAME_FN$
|
|
20968
|
+
const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20322
20969
|
const parts = [symbol, strategyName, exchangeName];
|
|
20323
20970
|
if (frameName) {
|
|
20324
20971
|
parts.push(frameName);
|
|
@@ -20338,7 +20985,7 @@ function percentile(sortedArray, p) {
|
|
|
20338
20985
|
return sortedArray[Math.max(0, index)];
|
|
20339
20986
|
}
|
|
20340
20987
|
/** Maximum number of performance events to store per strategy */
|
|
20341
|
-
const MAX_EVENTS$
|
|
20988
|
+
const MAX_EVENTS$7 = 10000;
|
|
20342
20989
|
/**
|
|
20343
20990
|
* Storage class for accumulating performance metrics per strategy.
|
|
20344
20991
|
* Maintains a list of all performance events and provides aggregated statistics.
|
|
@@ -20360,7 +21007,7 @@ class PerformanceStorage {
|
|
|
20360
21007
|
addEvent(event) {
|
|
20361
21008
|
this._events.unshift(event);
|
|
20362
21009
|
// Trim queue if exceeded MAX_EVENTS (keep most recent)
|
|
20363
|
-
if (this._events.length > MAX_EVENTS$
|
|
21010
|
+
if (this._events.length > MAX_EVENTS$7) {
|
|
20364
21011
|
this._events.pop();
|
|
20365
21012
|
}
|
|
20366
21013
|
}
|
|
@@ -20502,7 +21149,7 @@ class PerformanceStorage {
|
|
|
20502
21149
|
async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
20503
21150
|
const markdown = await this.getReport(strategyName, columns);
|
|
20504
21151
|
const timestamp = getContextTimestamp();
|
|
20505
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21152
|
+
const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20506
21153
|
await Markdown.writeData("performance", markdown, {
|
|
20507
21154
|
path,
|
|
20508
21155
|
file: filename,
|
|
@@ -20549,7 +21196,7 @@ class PerformanceMarkdownService {
|
|
|
20549
21196
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20550
21197
|
* Each combination gets its own isolated storage instance.
|
|
20551
21198
|
*/
|
|
20552
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21199
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
20553
21200
|
/**
|
|
20554
21201
|
* Subscribes to performance emitter to receive performance events.
|
|
20555
21202
|
* Protected against multiple subscriptions.
|
|
@@ -20716,7 +21363,7 @@ class PerformanceMarkdownService {
|
|
|
20716
21363
|
payload,
|
|
20717
21364
|
});
|
|
20718
21365
|
if (payload) {
|
|
20719
|
-
const key = CREATE_KEY_FN$
|
|
21366
|
+
const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20720
21367
|
this.getStorage.clear(key);
|
|
20721
21368
|
}
|
|
20722
21369
|
else {
|
|
@@ -20730,7 +21377,7 @@ class PerformanceMarkdownService {
|
|
|
20730
21377
|
* Creates a filename for markdown report based on walker name.
|
|
20731
21378
|
* Filename format: "walkerName-timestamp.md"
|
|
20732
21379
|
*/
|
|
20733
|
-
const CREATE_FILE_NAME_FN$
|
|
21380
|
+
const CREATE_FILE_NAME_FN$7 = (walkerName, timestamp) => {
|
|
20734
21381
|
return `${walkerName}-${timestamp}.md`;
|
|
20735
21382
|
};
|
|
20736
21383
|
/**
|
|
@@ -20765,7 +21412,7 @@ function formatMetric(value) {
|
|
|
20765
21412
|
* Storage class for accumulating walker results.
|
|
20766
21413
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
20767
21414
|
*/
|
|
20768
|
-
let ReportStorage$
|
|
21415
|
+
let ReportStorage$6 = class ReportStorage {
|
|
20769
21416
|
constructor(walkerName) {
|
|
20770
21417
|
this.walkerName = walkerName;
|
|
20771
21418
|
/** Walker metadata (set from first addResult call) */
|
|
@@ -20953,7 +21600,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
20953
21600
|
async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
20954
21601
|
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
20955
21602
|
const timestamp = getContextTimestamp();
|
|
20956
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21603
|
+
const filename = CREATE_FILE_NAME_FN$7(this.walkerName, timestamp);
|
|
20957
21604
|
await Markdown.writeData("walker", markdown, {
|
|
20958
21605
|
path,
|
|
20959
21606
|
file: filename,
|
|
@@ -20989,7 +21636,7 @@ class WalkerMarkdownService {
|
|
|
20989
21636
|
* Memoized function to get or create ReportStorage for a walker.
|
|
20990
21637
|
* Each walker gets its own isolated storage instance.
|
|
20991
21638
|
*/
|
|
20992
|
-
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$
|
|
21639
|
+
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$6(walkerName));
|
|
20993
21640
|
/**
|
|
20994
21641
|
* Subscribes to walker emitter to receive walker progress events.
|
|
20995
21642
|
* Protected against multiple subscriptions.
|
|
@@ -21186,7 +21833,7 @@ class WalkerMarkdownService {
|
|
|
21186
21833
|
* @param backtest - Whether running in backtest mode
|
|
21187
21834
|
* @returns Unique string key for memoization
|
|
21188
21835
|
*/
|
|
21189
|
-
const CREATE_KEY_FN$
|
|
21836
|
+
const CREATE_KEY_FN$e = (exchangeName, frameName, backtest) => {
|
|
21190
21837
|
const parts = [exchangeName];
|
|
21191
21838
|
if (frameName)
|
|
21192
21839
|
parts.push(frameName);
|
|
@@ -21197,7 +21844,7 @@ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
|
|
|
21197
21844
|
* Creates a filename for markdown report based on memoization key components.
|
|
21198
21845
|
* Filename format: "strategyName_exchangeName_frameName-timestamp.md"
|
|
21199
21846
|
*/
|
|
21200
|
-
const CREATE_FILE_NAME_FN$
|
|
21847
|
+
const CREATE_FILE_NAME_FN$6 = (strategyName, exchangeName, frameName, timestamp) => {
|
|
21201
21848
|
const parts = [strategyName, exchangeName];
|
|
21202
21849
|
if (frameName) {
|
|
21203
21850
|
parts.push(frameName);
|
|
@@ -21230,7 +21877,7 @@ function isUnsafe(value) {
|
|
|
21230
21877
|
return false;
|
|
21231
21878
|
}
|
|
21232
21879
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
21233
|
-
const MAX_EVENTS$
|
|
21880
|
+
const MAX_EVENTS$6 = 250;
|
|
21234
21881
|
/**
|
|
21235
21882
|
* Storage class for accumulating closed signals per strategy and generating heatmap.
|
|
21236
21883
|
* Maintains symbol-level statistics and provides portfolio-wide metrics.
|
|
@@ -21256,7 +21903,7 @@ class HeatmapStorage {
|
|
|
21256
21903
|
const signals = this.symbolData.get(symbol);
|
|
21257
21904
|
signals.unshift(data);
|
|
21258
21905
|
// Trim queue if exceeded MAX_EVENTS per symbol
|
|
21259
|
-
if (signals.length > MAX_EVENTS$
|
|
21906
|
+
if (signals.length > MAX_EVENTS$6) {
|
|
21260
21907
|
signals.pop();
|
|
21261
21908
|
}
|
|
21262
21909
|
}
|
|
@@ -21507,7 +22154,7 @@ class HeatmapStorage {
|
|
|
21507
22154
|
async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
21508
22155
|
const markdown = await this.getReport(strategyName, columns);
|
|
21509
22156
|
const timestamp = getContextTimestamp();
|
|
21510
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
22157
|
+
const filename = CREATE_FILE_NAME_FN$6(strategyName, this.exchangeName, this.frameName, timestamp);
|
|
21511
22158
|
await Markdown.writeData("heat", markdown, {
|
|
21512
22159
|
path,
|
|
21513
22160
|
file: filename,
|
|
@@ -21553,7 +22200,7 @@ class HeatMarkdownService {
|
|
|
21553
22200
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
21554
22201
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
21555
22202
|
*/
|
|
21556
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22203
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
21557
22204
|
/**
|
|
21558
22205
|
* Subscribes to signal emitter to receive tick events.
|
|
21559
22206
|
* Protected against multiple subscriptions.
|
|
@@ -21748,7 +22395,7 @@ class HeatMarkdownService {
|
|
|
21748
22395
|
payload,
|
|
21749
22396
|
});
|
|
21750
22397
|
if (payload) {
|
|
21751
|
-
const key = CREATE_KEY_FN$
|
|
22398
|
+
const key = CREATE_KEY_FN$e(payload.exchangeName, payload.frameName, payload.backtest);
|
|
21752
22399
|
this.getStorage.clear(key);
|
|
21753
22400
|
}
|
|
21754
22401
|
else {
|
|
@@ -22779,7 +23426,7 @@ class ClientPartial {
|
|
|
22779
23426
|
* @param backtest - Whether running in backtest mode
|
|
22780
23427
|
* @returns Unique string key for memoization
|
|
22781
23428
|
*/
|
|
22782
|
-
const CREATE_KEY_FN$
|
|
23429
|
+
const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
22783
23430
|
/**
|
|
22784
23431
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
22785
23432
|
*
|
|
@@ -22901,7 +23548,7 @@ class PartialConnectionService {
|
|
|
22901
23548
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
22902
23549
|
* Value: ClientPartial instance with logger and event emitters
|
|
22903
23550
|
*/
|
|
22904
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
23551
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
|
|
22905
23552
|
return new ClientPartial({
|
|
22906
23553
|
signalId,
|
|
22907
23554
|
logger: this.loggerService,
|
|
@@ -22991,7 +23638,7 @@ class PartialConnectionService {
|
|
|
22991
23638
|
const partial = this.getPartial(data.id, backtest);
|
|
22992
23639
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
22993
23640
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
22994
|
-
const key = CREATE_KEY_FN$
|
|
23641
|
+
const key = CREATE_KEY_FN$d(data.id, backtest);
|
|
22995
23642
|
this.getPartial.clear(key);
|
|
22996
23643
|
};
|
|
22997
23644
|
}
|
|
@@ -23007,7 +23654,7 @@ class PartialConnectionService {
|
|
|
23007
23654
|
* @param backtest - Whether running in backtest mode
|
|
23008
23655
|
* @returns Unique string key for memoization
|
|
23009
23656
|
*/
|
|
23010
|
-
const CREATE_KEY_FN$
|
|
23657
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
23011
23658
|
const parts = [symbol, strategyName, exchangeName];
|
|
23012
23659
|
if (frameName)
|
|
23013
23660
|
parts.push(frameName);
|
|
@@ -23018,7 +23665,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
23018
23665
|
* Creates a filename for markdown report based on memoization key components.
|
|
23019
23666
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
23020
23667
|
*/
|
|
23021
|
-
const CREATE_FILE_NAME_FN$
|
|
23668
|
+
const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
23022
23669
|
const parts = [symbol, strategyName, exchangeName];
|
|
23023
23670
|
if (frameName) {
|
|
23024
23671
|
parts.push(frameName);
|
|
@@ -23029,12 +23676,12 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
23029
23676
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
23030
23677
|
};
|
|
23031
23678
|
/** Maximum number of events to store in partial reports */
|
|
23032
|
-
const MAX_EVENTS$
|
|
23679
|
+
const MAX_EVENTS$5 = 250;
|
|
23033
23680
|
/**
|
|
23034
23681
|
* Storage class for accumulating partial profit/loss events per symbol-strategy pair.
|
|
23035
23682
|
* Maintains a chronological list of profit and loss level events.
|
|
23036
23683
|
*/
|
|
23037
|
-
let ReportStorage$
|
|
23684
|
+
let ReportStorage$5 = class ReportStorage {
|
|
23038
23685
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
23039
23686
|
this.symbol = symbol;
|
|
23040
23687
|
this.strategyName = strategyName;
|
|
@@ -23077,7 +23724,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23077
23724
|
backtest,
|
|
23078
23725
|
});
|
|
23079
23726
|
// Trim queue if exceeded MAX_EVENTS
|
|
23080
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
23727
|
+
if (this._eventList.length > MAX_EVENTS$5) {
|
|
23081
23728
|
this._eventList.pop();
|
|
23082
23729
|
}
|
|
23083
23730
|
}
|
|
@@ -23115,7 +23762,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23115
23762
|
backtest,
|
|
23116
23763
|
});
|
|
23117
23764
|
// Trim queue if exceeded MAX_EVENTS
|
|
23118
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
23765
|
+
if (this._eventList.length > MAX_EVENTS$5) {
|
|
23119
23766
|
this._eventList.pop();
|
|
23120
23767
|
}
|
|
23121
23768
|
}
|
|
@@ -23191,7 +23838,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23191
23838
|
async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
23192
23839
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
23193
23840
|
const timestamp = getContextTimestamp();
|
|
23194
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
23841
|
+
const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
23195
23842
|
await Markdown.writeData("partial", markdown, {
|
|
23196
23843
|
path,
|
|
23197
23844
|
file: filename,
|
|
@@ -23232,7 +23879,7 @@ class PartialMarkdownService {
|
|
|
23232
23879
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
23233
23880
|
* Each combination gets its own isolated storage instance.
|
|
23234
23881
|
*/
|
|
23235
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23882
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
23236
23883
|
/**
|
|
23237
23884
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
23238
23885
|
* Protected against multiple subscriptions.
|
|
@@ -23442,7 +24089,7 @@ class PartialMarkdownService {
|
|
|
23442
24089
|
payload,
|
|
23443
24090
|
});
|
|
23444
24091
|
if (payload) {
|
|
23445
|
-
const key = CREATE_KEY_FN$
|
|
24092
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
23446
24093
|
this.getStorage.clear(key);
|
|
23447
24094
|
}
|
|
23448
24095
|
else {
|
|
@@ -23458,7 +24105,7 @@ class PartialMarkdownService {
|
|
|
23458
24105
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
23459
24106
|
* @returns Unique string key for memoization
|
|
23460
24107
|
*/
|
|
23461
|
-
const CREATE_KEY_FN$
|
|
24108
|
+
const CREATE_KEY_FN$b = (context) => {
|
|
23462
24109
|
const parts = [context.strategyName, context.exchangeName];
|
|
23463
24110
|
if (context.frameName)
|
|
23464
24111
|
parts.push(context.frameName);
|
|
@@ -23532,7 +24179,7 @@ class PartialGlobalService {
|
|
|
23532
24179
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
23533
24180
|
* @param methodName - Name of the calling method for error tracking
|
|
23534
24181
|
*/
|
|
23535
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
24182
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
|
|
23536
24183
|
this.loggerService.log("partialGlobalService validate", {
|
|
23537
24184
|
context,
|
|
23538
24185
|
methodName,
|
|
@@ -23987,7 +24634,7 @@ class ClientBreakeven {
|
|
|
23987
24634
|
* @param backtest - Whether running in backtest mode
|
|
23988
24635
|
* @returns Unique string key for memoization
|
|
23989
24636
|
*/
|
|
23990
|
-
const CREATE_KEY_FN$
|
|
24637
|
+
const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
23991
24638
|
/**
|
|
23992
24639
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
23993
24640
|
*
|
|
@@ -24073,7 +24720,7 @@ class BreakevenConnectionService {
|
|
|
24073
24720
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24074
24721
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
24075
24722
|
*/
|
|
24076
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
24723
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
|
|
24077
24724
|
return new ClientBreakeven({
|
|
24078
24725
|
signalId,
|
|
24079
24726
|
logger: this.loggerService,
|
|
@@ -24134,7 +24781,7 @@ class BreakevenConnectionService {
|
|
|
24134
24781
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
24135
24782
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24136
24783
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
24137
|
-
const key = CREATE_KEY_FN$
|
|
24784
|
+
const key = CREATE_KEY_FN$a(data.id, backtest);
|
|
24138
24785
|
this.getBreakeven.clear(key);
|
|
24139
24786
|
};
|
|
24140
24787
|
}
|
|
@@ -24150,7 +24797,7 @@ class BreakevenConnectionService {
|
|
|
24150
24797
|
* @param backtest - Whether running in backtest mode
|
|
24151
24798
|
* @returns Unique string key for memoization
|
|
24152
24799
|
*/
|
|
24153
|
-
const CREATE_KEY_FN$
|
|
24800
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24154
24801
|
const parts = [symbol, strategyName, exchangeName];
|
|
24155
24802
|
if (frameName)
|
|
24156
24803
|
parts.push(frameName);
|
|
@@ -24161,7 +24808,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
24161
24808
|
* Creates a filename for markdown report based on memoization key components.
|
|
24162
24809
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
24163
24810
|
*/
|
|
24164
|
-
const CREATE_FILE_NAME_FN$
|
|
24811
|
+
const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
24165
24812
|
const parts = [symbol, strategyName, exchangeName];
|
|
24166
24813
|
if (frameName) {
|
|
24167
24814
|
parts.push(frameName);
|
|
@@ -24172,12 +24819,12 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
24172
24819
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
24173
24820
|
};
|
|
24174
24821
|
/** Maximum number of events to store in breakeven reports */
|
|
24175
|
-
const MAX_EVENTS$
|
|
24822
|
+
const MAX_EVENTS$4 = 250;
|
|
24176
24823
|
/**
|
|
24177
24824
|
* Storage class for accumulating breakeven events per symbol-strategy pair.
|
|
24178
24825
|
* Maintains a chronological list of breakeven events.
|
|
24179
24826
|
*/
|
|
24180
|
-
let ReportStorage$
|
|
24827
|
+
let ReportStorage$4 = class ReportStorage {
|
|
24181
24828
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
24182
24829
|
this.symbol = symbol;
|
|
24183
24830
|
this.strategyName = strategyName;
|
|
@@ -24218,7 +24865,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
24218
24865
|
backtest,
|
|
24219
24866
|
});
|
|
24220
24867
|
// Trim queue if exceeded MAX_EVENTS
|
|
24221
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
24868
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
24222
24869
|
this._eventList.pop();
|
|
24223
24870
|
}
|
|
24224
24871
|
}
|
|
@@ -24286,7 +24933,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
24286
24933
|
async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
|
|
24287
24934
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24288
24935
|
const timestamp = getContextTimestamp();
|
|
24289
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
24936
|
+
const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24290
24937
|
await Markdown.writeData("breakeven", markdown, {
|
|
24291
24938
|
path,
|
|
24292
24939
|
file: filename,
|
|
@@ -24327,7 +24974,7 @@ class BreakevenMarkdownService {
|
|
|
24327
24974
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24328
24975
|
* Each combination gets its own isolated storage instance.
|
|
24329
24976
|
*/
|
|
24330
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24977
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
24331
24978
|
/**
|
|
24332
24979
|
* Subscribes to breakeven signal emitter to receive events.
|
|
24333
24980
|
* Protected against multiple subscriptions.
|
|
@@ -24516,7 +25163,7 @@ class BreakevenMarkdownService {
|
|
|
24516
25163
|
payload,
|
|
24517
25164
|
});
|
|
24518
25165
|
if (payload) {
|
|
24519
|
-
const key = CREATE_KEY_FN$
|
|
25166
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24520
25167
|
this.getStorage.clear(key);
|
|
24521
25168
|
}
|
|
24522
25169
|
else {
|
|
@@ -24532,7 +25179,7 @@ class BreakevenMarkdownService {
|
|
|
24532
25179
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
24533
25180
|
* @returns Unique string key for memoization
|
|
24534
25181
|
*/
|
|
24535
|
-
const CREATE_KEY_FN$
|
|
25182
|
+
const CREATE_KEY_FN$8 = (context) => {
|
|
24536
25183
|
const parts = [context.strategyName, context.exchangeName];
|
|
24537
25184
|
if (context.frameName)
|
|
24538
25185
|
parts.push(context.frameName);
|
|
@@ -24606,7 +25253,7 @@ class BreakevenGlobalService {
|
|
|
24606
25253
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
24607
25254
|
* @param methodName - Name of the calling method for error tracking
|
|
24608
25255
|
*/
|
|
24609
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
25256
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
|
|
24610
25257
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
24611
25258
|
context,
|
|
24612
25259
|
methodName,
|
|
@@ -24826,7 +25473,7 @@ class ConfigValidationService {
|
|
|
24826
25473
|
* @param backtest - Whether running in backtest mode
|
|
24827
25474
|
* @returns Unique string key for memoization
|
|
24828
25475
|
*/
|
|
24829
|
-
const CREATE_KEY_FN$
|
|
25476
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24830
25477
|
const parts = [symbol, strategyName, exchangeName];
|
|
24831
25478
|
if (frameName)
|
|
24832
25479
|
parts.push(frameName);
|
|
@@ -24837,7 +25484,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
24837
25484
|
* Creates a filename for markdown report based on memoization key components.
|
|
24838
25485
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
24839
25486
|
*/
|
|
24840
|
-
const CREATE_FILE_NAME_FN$
|
|
25487
|
+
const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
24841
25488
|
const parts = [symbol, strategyName, exchangeName];
|
|
24842
25489
|
if (frameName) {
|
|
24843
25490
|
parts.push(frameName);
|
|
@@ -24848,12 +25495,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
24848
25495
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
24849
25496
|
};
|
|
24850
25497
|
/** Maximum number of events to store in risk reports */
|
|
24851
|
-
const MAX_EVENTS$
|
|
25498
|
+
const MAX_EVENTS$3 = 250;
|
|
24852
25499
|
/**
|
|
24853
25500
|
* Storage class for accumulating risk rejection events per symbol-strategy pair.
|
|
24854
25501
|
* Maintains a chronological list of rejected signals due to risk limits.
|
|
24855
25502
|
*/
|
|
24856
|
-
let ReportStorage$
|
|
25503
|
+
let ReportStorage$3 = class ReportStorage {
|
|
24857
25504
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
24858
25505
|
this.symbol = symbol;
|
|
24859
25506
|
this.strategyName = strategyName;
|
|
@@ -24870,7 +25517,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
24870
25517
|
addRejectionEvent(event) {
|
|
24871
25518
|
this._eventList.unshift(event);
|
|
24872
25519
|
// Trim queue if exceeded MAX_EVENTS
|
|
24873
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
25520
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
24874
25521
|
this._eventList.pop();
|
|
24875
25522
|
}
|
|
24876
25523
|
}
|
|
@@ -24954,7 +25601,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
24954
25601
|
async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
24955
25602
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24956
25603
|
const timestamp = getContextTimestamp();
|
|
24957
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
25604
|
+
const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24958
25605
|
await Markdown.writeData("risk", markdown, {
|
|
24959
25606
|
path,
|
|
24960
25607
|
file: filename,
|
|
@@ -24995,7 +25642,7 @@ class RiskMarkdownService {
|
|
|
24995
25642
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24996
25643
|
* Each combination gets its own isolated storage instance.
|
|
24997
25644
|
*/
|
|
24998
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25645
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
24999
25646
|
/**
|
|
25000
25647
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
25001
25648
|
* Protected against multiple subscriptions.
|
|
@@ -25184,7 +25831,7 @@ class RiskMarkdownService {
|
|
|
25184
25831
|
payload,
|
|
25185
25832
|
});
|
|
25186
25833
|
if (payload) {
|
|
25187
|
-
const key = CREATE_KEY_FN$
|
|
25834
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25188
25835
|
this.getStorage.clear(key);
|
|
25189
25836
|
}
|
|
25190
25837
|
else {
|
|
@@ -25482,6 +26129,7 @@ const WILDCARD_TARGET = {
|
|
|
25482
26129
|
schedule: true,
|
|
25483
26130
|
walker: true,
|
|
25484
26131
|
sync: true,
|
|
26132
|
+
highest_profit: true,
|
|
25485
26133
|
};
|
|
25486
26134
|
/**
|
|
25487
26135
|
* Utility class for managing report services.
|
|
@@ -25519,7 +26167,7 @@ class ReportUtils {
|
|
|
25519
26167
|
*
|
|
25520
26168
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
25521
26169
|
*/
|
|
25522
|
-
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, } = WILDCARD_TARGET) => {
|
|
26170
|
+
this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, } = WILDCARD_TARGET) => {
|
|
25523
26171
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
|
|
25524
26172
|
backtest: bt$1,
|
|
25525
26173
|
breakeven,
|
|
@@ -25567,6 +26215,9 @@ class ReportUtils {
|
|
|
25567
26215
|
if (sync) {
|
|
25568
26216
|
unList.push(bt.syncReportService.subscribe());
|
|
25569
26217
|
}
|
|
26218
|
+
if (highest_profit) {
|
|
26219
|
+
unList.push(bt.highestProfitReportService.subscribe());
|
|
26220
|
+
}
|
|
25570
26221
|
return compose(...unList.map((un) => () => void un()));
|
|
25571
26222
|
};
|
|
25572
26223
|
/**
|
|
@@ -25605,7 +26256,7 @@ class ReportUtils {
|
|
|
25605
26256
|
* Report.disable();
|
|
25606
26257
|
* ```
|
|
25607
26258
|
*/
|
|
25608
|
-
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, } = WILDCARD_TARGET) => {
|
|
26259
|
+
this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, } = WILDCARD_TARGET) => {
|
|
25609
26260
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
|
|
25610
26261
|
backtest: bt$1,
|
|
25611
26262
|
breakeven,
|
|
@@ -25652,6 +26303,9 @@ class ReportUtils {
|
|
|
25652
26303
|
if (sync) {
|
|
25653
26304
|
bt.syncReportService.unsubscribe();
|
|
25654
26305
|
}
|
|
26306
|
+
if (highest_profit) {
|
|
26307
|
+
bt.highestProfitReportService.unsubscribe();
|
|
26308
|
+
}
|
|
25655
26309
|
};
|
|
25656
26310
|
}
|
|
25657
26311
|
}
|
|
@@ -27859,6 +28513,60 @@ class SyncReportService {
|
|
|
27859
28513
|
}
|
|
27860
28514
|
}
|
|
27861
28515
|
|
|
28516
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE = "HighestProfitReportService.subscribe";
|
|
28517
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE = "HighestProfitReportService.unsubscribe";
|
|
28518
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK = "HighestProfitReportService.tick";
|
|
28519
|
+
/**
|
|
28520
|
+
* Service for logging highest profit events to the JSONL report database.
|
|
28521
|
+
*
|
|
28522
|
+
* Listens to highestProfitSubject and writes each new price record to
|
|
28523
|
+
* Report.writeData() for persistence and analytics.
|
|
28524
|
+
*/
|
|
28525
|
+
class HighestProfitReportService {
|
|
28526
|
+
constructor() {
|
|
28527
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
28528
|
+
this.tick = async (data) => {
|
|
28529
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK, { data });
|
|
28530
|
+
await Report.writeData("highest_profit", {
|
|
28531
|
+
timestamp: data.timestamp,
|
|
28532
|
+
symbol: data.symbol,
|
|
28533
|
+
strategyName: data.signal.strategyName,
|
|
28534
|
+
exchangeName: data.exchangeName,
|
|
28535
|
+
frameName: data.frameName,
|
|
28536
|
+
backtest: data.backtest,
|
|
28537
|
+
signalId: data.signal.id,
|
|
28538
|
+
position: data.signal.position,
|
|
28539
|
+
currentPrice: data.currentPrice,
|
|
28540
|
+
priceOpen: data.signal.priceOpen,
|
|
28541
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
28542
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
28543
|
+
}, {
|
|
28544
|
+
symbol: data.symbol,
|
|
28545
|
+
strategyName: data.signal.strategyName,
|
|
28546
|
+
exchangeName: data.exchangeName,
|
|
28547
|
+
frameName: data.frameName,
|
|
28548
|
+
signalId: data.signal.id,
|
|
28549
|
+
walkerName: "",
|
|
28550
|
+
});
|
|
28551
|
+
};
|
|
28552
|
+
this.subscribe = singleshot(() => {
|
|
28553
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE);
|
|
28554
|
+
const unsub = highestProfitSubject.subscribe(this.tick);
|
|
28555
|
+
return () => {
|
|
28556
|
+
this.subscribe.clear();
|
|
28557
|
+
unsub();
|
|
28558
|
+
};
|
|
28559
|
+
});
|
|
28560
|
+
this.unsubscribe = async () => {
|
|
28561
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE);
|
|
28562
|
+
if (this.subscribe.hasValue()) {
|
|
28563
|
+
const lastSubscription = this.subscribe();
|
|
28564
|
+
lastSubscription();
|
|
28565
|
+
}
|
|
28566
|
+
};
|
|
28567
|
+
}
|
|
28568
|
+
}
|
|
28569
|
+
|
|
27862
28570
|
/**
|
|
27863
28571
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
27864
28572
|
*
|
|
@@ -27872,7 +28580,7 @@ class SyncReportService {
|
|
|
27872
28580
|
* @returns Colon-separated key string for memoization
|
|
27873
28581
|
* @internal
|
|
27874
28582
|
*/
|
|
27875
|
-
const CREATE_KEY_FN$
|
|
28583
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
27876
28584
|
const parts = [symbol, strategyName, exchangeName];
|
|
27877
28585
|
if (frameName)
|
|
27878
28586
|
parts.push(frameName);
|
|
@@ -27892,7 +28600,7 @@ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
27892
28600
|
* @returns Underscore-separated filename with .md extension
|
|
27893
28601
|
* @internal
|
|
27894
28602
|
*/
|
|
27895
|
-
const CREATE_FILE_NAME_FN$
|
|
28603
|
+
const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
27896
28604
|
const parts = [symbol, strategyName, exchangeName];
|
|
27897
28605
|
if (frameName) {
|
|
27898
28606
|
parts.push(frameName);
|
|
@@ -27907,7 +28615,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
27907
28615
|
* Older events are discarded when this limit is exceeded.
|
|
27908
28616
|
* @internal
|
|
27909
28617
|
*/
|
|
27910
|
-
const MAX_EVENTS$
|
|
28618
|
+
const MAX_EVENTS$2 = 250;
|
|
27911
28619
|
/**
|
|
27912
28620
|
* In-memory storage for accumulating strategy events per symbol-strategy pair.
|
|
27913
28621
|
*
|
|
@@ -27920,7 +28628,7 @@ const MAX_EVENTS$1 = 250;
|
|
|
27920
28628
|
*
|
|
27921
28629
|
* @internal
|
|
27922
28630
|
*/
|
|
27923
|
-
let ReportStorage$
|
|
28631
|
+
let ReportStorage$2 = class ReportStorage {
|
|
27924
28632
|
/**
|
|
27925
28633
|
* Creates a new ReportStorage instance.
|
|
27926
28634
|
*
|
|
@@ -27946,7 +28654,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
27946
28654
|
*/
|
|
27947
28655
|
addEvent(event) {
|
|
27948
28656
|
this._eventList.unshift(event);
|
|
27949
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
28657
|
+
if (this._eventList.length > MAX_EVENTS$2) {
|
|
27950
28658
|
this._eventList.pop();
|
|
27951
28659
|
}
|
|
27952
28660
|
}
|
|
@@ -28051,7 +28759,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
28051
28759
|
async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
|
|
28052
28760
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
28053
28761
|
const timestamp = getContextTimestamp();
|
|
28054
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
28762
|
+
const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
28055
28763
|
await Markdown.writeData("strategy", markdown, {
|
|
28056
28764
|
path,
|
|
28057
28765
|
file: filename,
|
|
@@ -28120,7 +28828,7 @@ class StrategyMarkdownService {
|
|
|
28120
28828
|
*
|
|
28121
28829
|
* @internal
|
|
28122
28830
|
*/
|
|
28123
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
28831
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
|
|
28124
28832
|
/**
|
|
28125
28833
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
28126
28834
|
*
|
|
@@ -28688,7 +29396,7 @@ class StrategyMarkdownService {
|
|
|
28688
29396
|
this.clear = async (payload) => {
|
|
28689
29397
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
28690
29398
|
if (payload) {
|
|
28691
|
-
const key = CREATE_KEY_FN$
|
|
29399
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
28692
29400
|
this.getStorage.clear(key);
|
|
28693
29401
|
}
|
|
28694
29402
|
else {
|
|
@@ -28796,7 +29504,7 @@ class StrategyMarkdownService {
|
|
|
28796
29504
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
28797
29505
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
28798
29506
|
*/
|
|
28799
|
-
const CREATE_KEY_FN$
|
|
29507
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
28800
29508
|
const parts = [symbol, strategyName, exchangeName];
|
|
28801
29509
|
if (frameName)
|
|
28802
29510
|
parts.push(frameName);
|
|
@@ -28807,7 +29515,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
28807
29515
|
* Creates a filename for the markdown report.
|
|
28808
29516
|
* Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
|
|
28809
29517
|
*/
|
|
28810
|
-
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
29518
|
+
const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
28811
29519
|
const parts = [symbol, strategyName, exchangeName];
|
|
28812
29520
|
if (frameName) {
|
|
28813
29521
|
parts.push(frameName);
|
|
@@ -28818,12 +29526,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
|
|
|
28818
29526
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
28819
29527
|
};
|
|
28820
29528
|
/** Maximum number of events to store in sync reports */
|
|
28821
|
-
const MAX_EVENTS = 250;
|
|
29529
|
+
const MAX_EVENTS$1 = 250;
|
|
28822
29530
|
/**
|
|
28823
29531
|
* Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
|
|
28824
29532
|
* Maintains a chronological list of signal-open and signal-close events.
|
|
28825
29533
|
*/
|
|
28826
|
-
class ReportStorage {
|
|
29534
|
+
let ReportStorage$1 = class ReportStorage {
|
|
28827
29535
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
28828
29536
|
this.symbol = symbol;
|
|
28829
29537
|
this.strategyName = strategyName;
|
|
@@ -28833,7 +29541,7 @@ class ReportStorage {
|
|
|
28833
29541
|
}
|
|
28834
29542
|
addEvent(event) {
|
|
28835
29543
|
this._eventList.unshift(event);
|
|
28836
|
-
if (this._eventList.length > MAX_EVENTS) {
|
|
29544
|
+
if (this._eventList.length > MAX_EVENTS$1) {
|
|
28837
29545
|
this._eventList.pop();
|
|
28838
29546
|
}
|
|
28839
29547
|
}
|
|
@@ -28894,7 +29602,7 @@ class ReportStorage {
|
|
|
28894
29602
|
async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
|
|
28895
29603
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
28896
29604
|
const timestamp = getContextTimestamp();
|
|
28897
|
-
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
29605
|
+
const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
28898
29606
|
await Markdown.writeData("sync", markdown, {
|
|
28899
29607
|
path,
|
|
28900
29608
|
file: filename,
|
|
@@ -28905,7 +29613,7 @@ class ReportStorage {
|
|
|
28905
29613
|
frameName: this.frameName,
|
|
28906
29614
|
});
|
|
28907
29615
|
}
|
|
28908
|
-
}
|
|
29616
|
+
};
|
|
28909
29617
|
/**
|
|
28910
29618
|
* Service for generating and saving signal sync markdown reports.
|
|
28911
29619
|
*
|
|
@@ -28928,7 +29636,7 @@ class ReportStorage {
|
|
|
28928
29636
|
class SyncMarkdownService {
|
|
28929
29637
|
constructor() {
|
|
28930
29638
|
this.loggerService = inject(TYPES.loggerService);
|
|
28931
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
29639
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
28932
29640
|
this.subscribe = singleshot(() => {
|
|
28933
29641
|
this.loggerService.log("syncMarkdownService init");
|
|
28934
29642
|
const unsubscribe = syncSubject.subscribe(this.tick);
|
|
@@ -29002,6 +29710,189 @@ class SyncMarkdownService {
|
|
|
29002
29710
|
};
|
|
29003
29711
|
this.clear = async (payload) => {
|
|
29004
29712
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
29713
|
+
if (payload) {
|
|
29714
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29715
|
+
this.getStorage.clear(key);
|
|
29716
|
+
}
|
|
29717
|
+
else {
|
|
29718
|
+
this.getStorage.clear();
|
|
29719
|
+
}
|
|
29720
|
+
};
|
|
29721
|
+
}
|
|
29722
|
+
}
|
|
29723
|
+
|
|
29724
|
+
/**
|
|
29725
|
+
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
29726
|
+
*/
|
|
29727
|
+
const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29728
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29729
|
+
if (frameName)
|
|
29730
|
+
parts.push(frameName);
|
|
29731
|
+
parts.push(backtest ? "backtest" : "live");
|
|
29732
|
+
return parts.join(":");
|
|
29733
|
+
};
|
|
29734
|
+
/**
|
|
29735
|
+
* Creates a filename for the markdown report.
|
|
29736
|
+
*/
|
|
29737
|
+
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
29738
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29739
|
+
if (frameName) {
|
|
29740
|
+
parts.push(frameName);
|
|
29741
|
+
parts.push("backtest");
|
|
29742
|
+
}
|
|
29743
|
+
else
|
|
29744
|
+
parts.push("live");
|
|
29745
|
+
return `${parts.join("_")}-${timestamp}.md`;
|
|
29746
|
+
};
|
|
29747
|
+
/** Maximum number of events to store per combination */
|
|
29748
|
+
const MAX_EVENTS = 250;
|
|
29749
|
+
/**
|
|
29750
|
+
* Accumulates highest profit events per symbol-strategy-exchange-frame combination.
|
|
29751
|
+
*/
|
|
29752
|
+
class ReportStorage {
|
|
29753
|
+
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
29754
|
+
this.symbol = symbol;
|
|
29755
|
+
this.strategyName = strategyName;
|
|
29756
|
+
this.exchangeName = exchangeName;
|
|
29757
|
+
this.frameName = frameName;
|
|
29758
|
+
this._eventList = [];
|
|
29759
|
+
}
|
|
29760
|
+
/**
|
|
29761
|
+
* Adds a highest profit event to the storage.
|
|
29762
|
+
*/
|
|
29763
|
+
addEvent(data, currentPrice, backtest, timestamp) {
|
|
29764
|
+
this._eventList.unshift({
|
|
29765
|
+
timestamp,
|
|
29766
|
+
symbol: data.symbol,
|
|
29767
|
+
strategyName: data.strategyName,
|
|
29768
|
+
signalId: data.id,
|
|
29769
|
+
position: data.position,
|
|
29770
|
+
currentPrice,
|
|
29771
|
+
priceOpen: data.priceOpen,
|
|
29772
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
29773
|
+
priceStopLoss: data.priceStopLoss,
|
|
29774
|
+
backtest,
|
|
29775
|
+
});
|
|
29776
|
+
if (this._eventList.length > MAX_EVENTS) {
|
|
29777
|
+
this._eventList.pop();
|
|
29778
|
+
}
|
|
29779
|
+
}
|
|
29780
|
+
/**
|
|
29781
|
+
* Returns aggregated statistics from accumulated events.
|
|
29782
|
+
*/
|
|
29783
|
+
async getData() {
|
|
29784
|
+
return {
|
|
29785
|
+
eventList: this._eventList,
|
|
29786
|
+
totalEvents: this._eventList.length,
|
|
29787
|
+
};
|
|
29788
|
+
}
|
|
29789
|
+
/**
|
|
29790
|
+
* Generates a markdown report table for this storage.
|
|
29791
|
+
*/
|
|
29792
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.highest_profit_columns) {
|
|
29793
|
+
const stats = await this.getData();
|
|
29794
|
+
if (stats.totalEvents === 0) {
|
|
29795
|
+
return [
|
|
29796
|
+
`# Highest Profit Report: ${symbol}:${strategyName}`,
|
|
29797
|
+
"",
|
|
29798
|
+
"No highest profit events recorded yet.",
|
|
29799
|
+
].join("\n");
|
|
29800
|
+
}
|
|
29801
|
+
const visibleColumns = [];
|
|
29802
|
+
for (const col of columns) {
|
|
29803
|
+
if (await col.isVisible()) {
|
|
29804
|
+
visibleColumns.push(col);
|
|
29805
|
+
}
|
|
29806
|
+
}
|
|
29807
|
+
const header = visibleColumns.map((col) => col.label);
|
|
29808
|
+
const separator = visibleColumns.map(() => "---");
|
|
29809
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
29810
|
+
const tableData = [header, separator, ...rows];
|
|
29811
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
29812
|
+
return [
|
|
29813
|
+
`# Highest Profit Report: ${symbol}:${strategyName}`,
|
|
29814
|
+
"",
|
|
29815
|
+
table,
|
|
29816
|
+
"",
|
|
29817
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
29818
|
+
].join("\n");
|
|
29819
|
+
}
|
|
29820
|
+
/**
|
|
29821
|
+
* Saves the report to disk.
|
|
29822
|
+
*/
|
|
29823
|
+
async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
|
|
29824
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
29825
|
+
const timestamp = getContextTimestamp();
|
|
29826
|
+
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
29827
|
+
await Markdown.writeData("highest_profit", markdown, {
|
|
29828
|
+
path,
|
|
29829
|
+
file: filename,
|
|
29830
|
+
symbol: this.symbol,
|
|
29831
|
+
signalId: "",
|
|
29832
|
+
strategyName: this.strategyName,
|
|
29833
|
+
exchangeName: this.exchangeName,
|
|
29834
|
+
frameName: this.frameName,
|
|
29835
|
+
});
|
|
29836
|
+
}
|
|
29837
|
+
}
|
|
29838
|
+
/**
|
|
29839
|
+
* Service for generating and saving highest profit markdown reports.
|
|
29840
|
+
*
|
|
29841
|
+
* Listens to highestProfitSubject and accumulates events per
|
|
29842
|
+
* symbol-strategy-exchange-frame combination. Provides getData(),
|
|
29843
|
+
* getReport(), and dump() methods matching the Partial pattern.
|
|
29844
|
+
*/
|
|
29845
|
+
class HighestProfitMarkdownService {
|
|
29846
|
+
constructor() {
|
|
29847
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
29848
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
29849
|
+
this.subscribe = singleshot(() => {
|
|
29850
|
+
this.loggerService.log("highestProfitMarkdownService init");
|
|
29851
|
+
const unsub = highestProfitSubject.subscribe(this.tick);
|
|
29852
|
+
return () => {
|
|
29853
|
+
this.subscribe.clear();
|
|
29854
|
+
this.clear();
|
|
29855
|
+
unsub();
|
|
29856
|
+
};
|
|
29857
|
+
});
|
|
29858
|
+
this.unsubscribe = async () => {
|
|
29859
|
+
this.loggerService.log("highestProfitMarkdownService unsubscribe");
|
|
29860
|
+
if (this.subscribe.hasValue()) {
|
|
29861
|
+
const lastSubscription = this.subscribe();
|
|
29862
|
+
lastSubscription();
|
|
29863
|
+
}
|
|
29864
|
+
};
|
|
29865
|
+
this.tick = async (data) => {
|
|
29866
|
+
this.loggerService.log("highestProfitMarkdownService tick", { data });
|
|
29867
|
+
const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
|
|
29868
|
+
storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
|
|
29869
|
+
};
|
|
29870
|
+
this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29871
|
+
this.loggerService.log("highestProfitMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
29872
|
+
if (!this.subscribe.hasValue()) {
|
|
29873
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before getting data.");
|
|
29874
|
+
}
|
|
29875
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29876
|
+
return storage.getData();
|
|
29877
|
+
};
|
|
29878
|
+
this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.highest_profit_columns) => {
|
|
29879
|
+
this.loggerService.log("highestProfitMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
29880
|
+
if (!this.subscribe.hasValue()) {
|
|
29881
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before generating reports.");
|
|
29882
|
+
}
|
|
29883
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29884
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
29885
|
+
};
|
|
29886
|
+
this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) => {
|
|
29887
|
+
this.loggerService.log("highestProfitMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
|
|
29888
|
+
if (!this.subscribe.hasValue()) {
|
|
29889
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before dumping reports.");
|
|
29890
|
+
}
|
|
29891
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29892
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
29893
|
+
};
|
|
29894
|
+
this.clear = async (payload) => {
|
|
29895
|
+
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
29005
29896
|
if (payload) {
|
|
29006
29897
|
const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29007
29898
|
this.getStorage.clear(key);
|
|
@@ -29351,6 +30242,7 @@ class TimeMetaService {
|
|
|
29351
30242
|
provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
|
|
29352
30243
|
provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
|
|
29353
30244
|
provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
|
|
30245
|
+
provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
|
|
29354
30246
|
}
|
|
29355
30247
|
{
|
|
29356
30248
|
provide(TYPES.backtestReportService, () => new BacktestReportService());
|
|
@@ -29364,6 +30256,7 @@ class TimeMetaService {
|
|
|
29364
30256
|
provide(TYPES.riskReportService, () => new RiskReportService());
|
|
29365
30257
|
provide(TYPES.strategyReportService, () => new StrategyReportService());
|
|
29366
30258
|
provide(TYPES.syncReportService, () => new SyncReportService());
|
|
30259
|
+
provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
|
|
29367
30260
|
}
|
|
29368
30261
|
{
|
|
29369
30262
|
provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
|
|
@@ -29446,6 +30339,7 @@ const markdownServices = {
|
|
|
29446
30339
|
riskMarkdownService: inject(TYPES.riskMarkdownService),
|
|
29447
30340
|
strategyMarkdownService: inject(TYPES.strategyMarkdownService),
|
|
29448
30341
|
syncMarkdownService: inject(TYPES.syncMarkdownService),
|
|
30342
|
+
highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
|
|
29449
30343
|
};
|
|
29450
30344
|
const reportServices = {
|
|
29451
30345
|
backtestReportService: inject(TYPES.backtestReportService),
|
|
@@ -29459,6 +30353,7 @@ const reportServices = {
|
|
|
29459
30353
|
riskReportService: inject(TYPES.riskReportService),
|
|
29460
30354
|
strategyReportService: inject(TYPES.strategyReportService),
|
|
29461
30355
|
syncReportService: inject(TYPES.syncReportService),
|
|
30356
|
+
highestProfitReportService: inject(TYPES.highestProfitReportService),
|
|
29462
30357
|
};
|
|
29463
30358
|
const validationServices = {
|
|
29464
30359
|
exchangeValidationService: inject(TYPES.exchangeValidationService),
|
|
@@ -31230,7 +32125,7 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
|
|
|
31230
32125
|
*
|
|
31231
32126
|
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
31232
32127
|
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
31233
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32128
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31234
32129
|
* @returns percentShift to pass to `commitTrailingStop`
|
|
31235
32130
|
*
|
|
31236
32131
|
* @example
|
|
@@ -31252,7 +32147,7 @@ const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectiv
|
|
|
31252
32147
|
*
|
|
31253
32148
|
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
31254
32149
|
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
31255
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32150
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31256
32151
|
* @returns percentShift to pass to `commitTrailingTake`
|
|
31257
32152
|
*
|
|
31258
32153
|
* @example
|
|
@@ -31277,7 +32172,7 @@ const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effe
|
|
|
31277
32172
|
*
|
|
31278
32173
|
* @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
|
|
31279
32174
|
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
31280
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32175
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31281
32176
|
* @param position - Position direction: "long" or "short"
|
|
31282
32177
|
* @returns Absolute stop-loss price corresponding to the given percentShift
|
|
31283
32178
|
*
|
|
@@ -31304,7 +32199,7 @@ const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePri
|
|
|
31304
32199
|
*
|
|
31305
32200
|
* @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
|
|
31306
32201
|
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
31307
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32202
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31308
32203
|
* @param position - Position direction: "long" or "short"
|
|
31309
32204
|
* @returns Absolute take-profit price corresponding to the given percentShift
|
|
31310
32205
|
*
|
|
@@ -31343,7 +32238,7 @@ const percentToCloseCost = (percentToClose, investedCost) => {
|
|
|
31343
32238
|
* Breakeven moves the SL to the effective entry price (effectivePriceOpen).
|
|
31344
32239
|
* The value is the same regardless of position direction.
|
|
31345
32240
|
*
|
|
31346
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32241
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31347
32242
|
* @returns New stop-loss price equal to the effective entry price
|
|
31348
32243
|
*
|
|
31349
32244
|
* @example
|
|
@@ -32265,13 +33160,22 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
|
|
|
32265
33160
|
const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
|
|
32266
33161
|
const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
|
|
32267
33162
|
const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
|
|
32268
|
-
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.
|
|
33163
|
+
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionEffectivePrice";
|
|
32269
33164
|
const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
|
|
32270
33165
|
const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
|
|
32271
33166
|
const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
32272
33167
|
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
32273
33168
|
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
32274
33169
|
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
33170
|
+
const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
|
|
33171
|
+
const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
|
|
33172
|
+
const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
|
|
33173
|
+
const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
|
|
33174
|
+
const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
|
|
33175
|
+
const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
|
|
33176
|
+
const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
|
|
33177
|
+
const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
|
|
33178
|
+
const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
|
|
32275
33179
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
32276
33180
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
32277
33181
|
/**
|
|
@@ -32538,7 +33442,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
32538
33442
|
if (!signal) {
|
|
32539
33443
|
return false;
|
|
32540
33444
|
}
|
|
32541
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33445
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32542
33446
|
if (effectivePriceOpen === null) {
|
|
32543
33447
|
return false;
|
|
32544
33448
|
}
|
|
@@ -32618,7 +33522,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
32618
33522
|
if (!signal) {
|
|
32619
33523
|
return false;
|
|
32620
33524
|
}
|
|
32621
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33525
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32622
33526
|
if (effectivePriceOpen === null) {
|
|
32623
33527
|
return false;
|
|
32624
33528
|
}
|
|
@@ -32668,7 +33572,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
|
32668
33572
|
if (!signal) {
|
|
32669
33573
|
return false;
|
|
32670
33574
|
}
|
|
32671
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33575
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32672
33576
|
if (effectivePriceOpen === null) {
|
|
32673
33577
|
return false;
|
|
32674
33578
|
}
|
|
@@ -32719,7 +33623,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
|
32719
33623
|
if (!signal) {
|
|
32720
33624
|
return false;
|
|
32721
33625
|
}
|
|
32722
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33626
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32723
33627
|
if (effectivePriceOpen === null) {
|
|
32724
33628
|
return false;
|
|
32725
33629
|
}
|
|
@@ -32781,7 +33685,7 @@ async function commitBreakeven(symbol) {
|
|
|
32781
33685
|
if (!signal) {
|
|
32782
33686
|
return false;
|
|
32783
33687
|
}
|
|
32784
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33688
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32785
33689
|
if (effectivePriceOpen === null) {
|
|
32786
33690
|
return false;
|
|
32787
33691
|
}
|
|
@@ -33077,26 +33981,26 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
33077
33981
|
*
|
|
33078
33982
|
* @example
|
|
33079
33983
|
* ```typescript
|
|
33080
|
-
* import {
|
|
33984
|
+
* import { getPositionEffectivePrice } from "backtest-kit";
|
|
33081
33985
|
*
|
|
33082
|
-
* const avgPrice = await
|
|
33986
|
+
* const avgPrice = await getPositionEffectivePrice("BTCUSDT");
|
|
33083
33987
|
* // No DCA: avgPrice === priceOpen
|
|
33084
33988
|
* // After DCA at lower price: avgPrice < priceOpen
|
|
33085
33989
|
* ```
|
|
33086
33990
|
*/
|
|
33087
|
-
async function
|
|
33991
|
+
async function getPositionEffectivePrice(symbol) {
|
|
33088
33992
|
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
33089
33993
|
symbol,
|
|
33090
33994
|
});
|
|
33091
33995
|
if (!ExecutionContextService.hasContext()) {
|
|
33092
|
-
throw new Error("
|
|
33996
|
+
throw new Error("getPositionEffectivePrice requires an execution context");
|
|
33093
33997
|
}
|
|
33094
33998
|
if (!MethodContextService.hasContext()) {
|
|
33095
|
-
throw new Error("
|
|
33999
|
+
throw new Error("getPositionEffectivePrice requires a method context");
|
|
33096
34000
|
}
|
|
33097
34001
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33098
34002
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33099
|
-
return await bt.strategyCoreService.
|
|
34003
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33100
34004
|
}
|
|
33101
34005
|
/**
|
|
33102
34006
|
* Returns the number of DCA entries made for the current pending signal.
|
|
@@ -33451,6 +34355,284 @@ async function getPositionPartials(symbol) {
|
|
|
33451
34355
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33452
34356
|
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33453
34357
|
}
|
|
34358
|
+
/**
|
|
34359
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
34360
|
+
*
|
|
34361
|
+
* Each element represents a single position entry — the initial open or a subsequent
|
|
34362
|
+
* DCA entry added via commitAverageBuy.
|
|
34363
|
+
*
|
|
34364
|
+
* Returns null if no pending signal exists.
|
|
34365
|
+
* Returns a single-element array if no DCA entries were made.
|
|
34366
|
+
*
|
|
34367
|
+
* Each entry contains:
|
|
34368
|
+
* - `price` — execution price of this entry
|
|
34369
|
+
* - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
|
|
34370
|
+
*
|
|
34371
|
+
* @param symbol - Trading pair symbol
|
|
34372
|
+
* @returns Promise resolving to array of entry records or null
|
|
34373
|
+
*
|
|
34374
|
+
* @example
|
|
34375
|
+
* ```typescript
|
|
34376
|
+
* import { getPositionEntries } from "backtest-kit";
|
|
34377
|
+
*
|
|
34378
|
+
* const entries = await getPositionEntries("BTCUSDT");
|
|
34379
|
+
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
34380
|
+
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
34381
|
+
* ```
|
|
34382
|
+
*/
|
|
34383
|
+
async function getPositionEntries(symbol) {
|
|
34384
|
+
bt.loggerService.info(GET_POSITION_ENTRIES_METHOD_NAME, { symbol });
|
|
34385
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34386
|
+
throw new Error("getPositionEntries requires an execution context");
|
|
34387
|
+
}
|
|
34388
|
+
if (!MethodContextService.hasContext()) {
|
|
34389
|
+
throw new Error("getPositionEntries requires a method context");
|
|
34390
|
+
}
|
|
34391
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34392
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34393
|
+
return await bt.strategyCoreService.getPositionEntries(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34394
|
+
}
|
|
34395
|
+
/**
|
|
34396
|
+
* Returns the original estimated duration for the current pending signal.
|
|
34397
|
+
*
|
|
34398
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
34399
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
34400
|
+
*
|
|
34401
|
+
* Returns null if no pending signal exists.
|
|
34402
|
+
*
|
|
34403
|
+
* @param symbol - Trading pair symbol
|
|
34404
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
34405
|
+
*
|
|
34406
|
+
* @example
|
|
34407
|
+
* ```typescript
|
|
34408
|
+
* import { getPositionEstimateMinutes } from "backtest-kit";
|
|
34409
|
+
*
|
|
34410
|
+
* const estimate = await getPositionEstimateMinutes("BTCUSDT");
|
|
34411
|
+
* // e.g. 120 (2 hours)
|
|
34412
|
+
* ```
|
|
34413
|
+
*/
|
|
34414
|
+
async function getPositionEstimateMinutes(symbol) {
|
|
34415
|
+
bt.loggerService.info(GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME, { symbol });
|
|
34416
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34417
|
+
throw new Error("getPositionEstimateMinutes requires an execution context");
|
|
34418
|
+
}
|
|
34419
|
+
if (!MethodContextService.hasContext()) {
|
|
34420
|
+
throw new Error("getPositionEstimateMinutes requires a method context");
|
|
34421
|
+
}
|
|
34422
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34423
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34424
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34425
|
+
}
|
|
34426
|
+
/**
|
|
34427
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
34428
|
+
*
|
|
34429
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
34430
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
34431
|
+
*
|
|
34432
|
+
* Returns null if no pending signal exists.
|
|
34433
|
+
*
|
|
34434
|
+
* @param symbol - Trading pair symbol
|
|
34435
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
34436
|
+
*
|
|
34437
|
+
* @example
|
|
34438
|
+
* ```typescript
|
|
34439
|
+
* import { getPositionCountdownMinutes } from "backtest-kit";
|
|
34440
|
+
*
|
|
34441
|
+
* const remaining = await getPositionCountdownMinutes("BTCUSDT");
|
|
34442
|
+
* // e.g. 45 (45 minutes left)
|
|
34443
|
+
* // 0 when expired
|
|
34444
|
+
* ```
|
|
34445
|
+
*/
|
|
34446
|
+
async function getPositionCountdownMinutes(symbol) {
|
|
34447
|
+
bt.loggerService.info(GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME, { symbol });
|
|
34448
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34449
|
+
throw new Error("getPositionCountdownMinutes requires an execution context");
|
|
34450
|
+
}
|
|
34451
|
+
if (!MethodContextService.hasContext()) {
|
|
34452
|
+
throw new Error("getPositionCountdownMinutes requires a method context");
|
|
34453
|
+
}
|
|
34454
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34455
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34456
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34457
|
+
}
|
|
34458
|
+
/**
|
|
34459
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
34460
|
+
*
|
|
34461
|
+
* Initialized at position open with the entry price and timestamp.
|
|
34462
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
34463
|
+
* - LONG: tracks the highest price seen above effective entry
|
|
34464
|
+
* - SHORT: tracks the lowest price seen below effective entry
|
|
34465
|
+
*
|
|
34466
|
+
* Returns null if no pending signal exists.
|
|
34467
|
+
* Never returns null when a signal is active — always contains at least the entry price.
|
|
34468
|
+
*
|
|
34469
|
+
* @param symbol - Trading pair symbol
|
|
34470
|
+
* @returns Promise resolving to `{ price, timestamp }` record or null
|
|
34471
|
+
*
|
|
34472
|
+
* @example
|
|
34473
|
+
* ```typescript
|
|
34474
|
+
* import { getPositionHighestProfitPrice } from "backtest-kit";
|
|
34475
|
+
*
|
|
34476
|
+
* const peak = await getPositionHighestProfitPrice("BTCUSDT");
|
|
34477
|
+
* // e.g. { price: 44500, timestamp: 1700000000000 }
|
|
34478
|
+
* ```
|
|
34479
|
+
*/
|
|
34480
|
+
async function getPositionHighestProfitPrice(symbol) {
|
|
34481
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME, { symbol });
|
|
34482
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34483
|
+
throw new Error("getPositionHighestProfitPrice requires an execution context");
|
|
34484
|
+
}
|
|
34485
|
+
if (!MethodContextService.hasContext()) {
|
|
34486
|
+
throw new Error("getPositionHighestProfitPrice requires a method context");
|
|
34487
|
+
}
|
|
34488
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34489
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34490
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34491
|
+
}
|
|
34492
|
+
/**
|
|
34493
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
34494
|
+
*
|
|
34495
|
+
* Returns null if no pending signal exists.
|
|
34496
|
+
*
|
|
34497
|
+
* @param symbol - Trading pair symbol
|
|
34498
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
34499
|
+
*
|
|
34500
|
+
* @example
|
|
34501
|
+
* ```typescript
|
|
34502
|
+
* import { getPositionHighestProfitTimestamp } from "backtest-kit";
|
|
34503
|
+
*
|
|
34504
|
+
* const ts = await getPositionHighestProfitTimestamp("BTCUSDT");
|
|
34505
|
+
* // e.g. 1700000000000
|
|
34506
|
+
* ```
|
|
34507
|
+
*/
|
|
34508
|
+
async function getPositionHighestProfitTimestamp(symbol) {
|
|
34509
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME, { symbol });
|
|
34510
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34511
|
+
throw new Error("getPositionHighestProfitTimestamp requires an execution context");
|
|
34512
|
+
}
|
|
34513
|
+
if (!MethodContextService.hasContext()) {
|
|
34514
|
+
throw new Error("getPositionHighestProfitTimestamp requires a method context");
|
|
34515
|
+
}
|
|
34516
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34517
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34518
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34519
|
+
}
|
|
34520
|
+
/**
|
|
34521
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
34522
|
+
*
|
|
34523
|
+
* Returns null if no pending signal exists.
|
|
34524
|
+
*
|
|
34525
|
+
* @param symbol - Trading pair symbol
|
|
34526
|
+
* @returns Promise resolving to PnL percentage or null
|
|
34527
|
+
*
|
|
34528
|
+
* @example
|
|
34529
|
+
* ```typescript
|
|
34530
|
+
* import { getPositionHighestPnlPercentage } from "backtest-kit";
|
|
34531
|
+
*
|
|
34532
|
+
* const pnl = await getPositionHighestPnlPercentage("BTCUSDT");
|
|
34533
|
+
* // e.g. 3.5
|
|
34534
|
+
* ```
|
|
34535
|
+
*/
|
|
34536
|
+
async function getPositionHighestPnlPercentage(symbol) {
|
|
34537
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
34538
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34539
|
+
throw new Error("getPositionHighestPnlPercentage requires an execution context");
|
|
34540
|
+
}
|
|
34541
|
+
if (!MethodContextService.hasContext()) {
|
|
34542
|
+
throw new Error("getPositionHighestPnlPercentage requires a method context");
|
|
34543
|
+
}
|
|
34544
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34545
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34546
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34547
|
+
}
|
|
34548
|
+
/**
|
|
34549
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
34550
|
+
*
|
|
34551
|
+
* Returns null if no pending signal exists.
|
|
34552
|
+
*
|
|
34553
|
+
* @param symbol - Trading pair symbol
|
|
34554
|
+
* @returns Promise resolving to PnL cost or null
|
|
34555
|
+
*
|
|
34556
|
+
* @example
|
|
34557
|
+
* ```typescript
|
|
34558
|
+
* import { getPositionHighestPnlCost } from "backtest-kit";
|
|
34559
|
+
*
|
|
34560
|
+
* const pnlCost = await getPositionHighestPnlCost("BTCUSDT");
|
|
34561
|
+
* // e.g. 35.5
|
|
34562
|
+
* ```
|
|
34563
|
+
*/
|
|
34564
|
+
async function getPositionHighestPnlCost(symbol) {
|
|
34565
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME, { symbol });
|
|
34566
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34567
|
+
throw new Error("getPositionHighestPnlCost requires an execution context");
|
|
34568
|
+
}
|
|
34569
|
+
if (!MethodContextService.hasContext()) {
|
|
34570
|
+
throw new Error("getPositionHighestPnlCost requires a method context");
|
|
34571
|
+
}
|
|
34572
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34573
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34574
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34575
|
+
}
|
|
34576
|
+
/**
|
|
34577
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
34578
|
+
*
|
|
34579
|
+
* Returns null if no pending signal exists.
|
|
34580
|
+
*
|
|
34581
|
+
* @param symbol - Trading pair symbol
|
|
34582
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
34583
|
+
*
|
|
34584
|
+
* @example
|
|
34585
|
+
* ```typescript
|
|
34586
|
+
* import { getPositionHighestProfitBreakeven } from "backtest-kit";
|
|
34587
|
+
*
|
|
34588
|
+
* const couldBreakeven = await getPositionHighestProfitBreakeven("BTCUSDT");
|
|
34589
|
+
* // e.g. true (price reached the breakeven threshold at its peak)
|
|
34590
|
+
* ```
|
|
34591
|
+
*/
|
|
34592
|
+
async function getPositionHighestProfitBreakeven(symbol) {
|
|
34593
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME, { symbol });
|
|
34594
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34595
|
+
throw new Error("getPositionHighestProfitBreakeven requires an execution context");
|
|
34596
|
+
}
|
|
34597
|
+
if (!MethodContextService.hasContext()) {
|
|
34598
|
+
throw new Error("getPositionHighestProfitBreakeven requires a method context");
|
|
34599
|
+
}
|
|
34600
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34601
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34602
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34603
|
+
}
|
|
34604
|
+
/**
|
|
34605
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
34606
|
+
*
|
|
34607
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
34608
|
+
* Zero when called at the exact moment the peak was set.
|
|
34609
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
34610
|
+
*
|
|
34611
|
+
* Returns null if no pending signal exists.
|
|
34612
|
+
*
|
|
34613
|
+
* @param symbol - Trading pair symbol
|
|
34614
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
34615
|
+
*
|
|
34616
|
+
* @example
|
|
34617
|
+
* ```typescript
|
|
34618
|
+
* import { getPositionDrawdownMinutes } from "backtest-kit";
|
|
34619
|
+
*
|
|
34620
|
+
* const drawdown = await getPositionDrawdownMinutes("BTCUSDT");
|
|
34621
|
+
* // e.g. 30 (30 minutes since the highest profit price)
|
|
34622
|
+
* ```
|
|
34623
|
+
*/
|
|
34624
|
+
async function getPositionDrawdownMinutes(symbol) {
|
|
34625
|
+
bt.loggerService.info(GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
|
|
34626
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34627
|
+
throw new Error("getPositionDrawdownMinutes requires an execution context");
|
|
34628
|
+
}
|
|
34629
|
+
if (!MethodContextService.hasContext()) {
|
|
34630
|
+
throw new Error("getPositionDrawdownMinutes requires a method context");
|
|
34631
|
+
}
|
|
34632
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34633
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34634
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34635
|
+
}
|
|
33454
34636
|
/**
|
|
33455
34637
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
33456
34638
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -33628,6 +34810,8 @@ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
|
|
|
33628
34810
|
const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
|
|
33629
34811
|
const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
|
|
33630
34812
|
const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
|
|
34813
|
+
const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
|
|
34814
|
+
const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
|
|
33631
34815
|
/**
|
|
33632
34816
|
* Subscribes to all signal events with queued async processing.
|
|
33633
34817
|
*
|
|
@@ -34754,6 +35938,33 @@ function listenSyncOnce(filterFn, fn) {
|
|
|
34754
35938
|
bt.loggerService.log(LISTEN_SYNC_ONCE_METHOD_NAME);
|
|
34755
35939
|
return syncSubject.filter(filterFn).once(fn);
|
|
34756
35940
|
}
|
|
35941
|
+
/**
|
|
35942
|
+
* Subscribes to highest profit events with queued async processing.
|
|
35943
|
+
* Emits when a signal reaches a new highest profit level during its lifecycle.
|
|
35944
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
35945
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
35946
|
+
* Useful for tracking profit milestones and implementing dynamic management logic.
|
|
35947
|
+
*
|
|
35948
|
+
* @param fn - Callback function to handle highest profit events
|
|
35949
|
+
* @return Unsubscribe function to stop listening to events
|
|
35950
|
+
*/
|
|
35951
|
+
function listenHighestProfit(fn) {
|
|
35952
|
+
bt.loggerService.log(LISTEN_HIGHEST_PROFIT_METHOD_NAME);
|
|
35953
|
+
return highestProfitSubject.subscribe(queued(async (event) => fn(event)));
|
|
35954
|
+
}
|
|
35955
|
+
/**
|
|
35956
|
+
* Subscribes to filtered highest profit events with one-time execution.
|
|
35957
|
+
* Listens for events matching the filter predicate, then executes callback once
|
|
35958
|
+
* and automatically unsubscribes. Useful for waiting for specific profit conditions.
|
|
35959
|
+
*
|
|
35960
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
35961
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
35962
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
35963
|
+
*/
|
|
35964
|
+
function listenHighestProfitOnce(filterFn, fn) {
|
|
35965
|
+
bt.loggerService.log(LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME);
|
|
35966
|
+
return highestProfitSubject.filter(filterFn).once(fn);
|
|
35967
|
+
}
|
|
34757
35968
|
|
|
34758
35969
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
34759
35970
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
@@ -34767,7 +35978,7 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
|
|
|
34767
35978
|
const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
|
|
34768
35979
|
const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
|
|
34769
35980
|
const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
|
|
34770
|
-
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.
|
|
35981
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionEffectivePrice";
|
|
34771
35982
|
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
|
|
34772
35983
|
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
|
|
34773
35984
|
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
|
|
@@ -34775,6 +35986,14 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
|
|
|
34775
35986
|
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
34776
35987
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
34777
35988
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
|
|
35989
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
|
|
35990
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
|
|
35991
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
|
|
35992
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
|
|
35993
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
|
|
35994
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
|
|
35995
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
|
|
35996
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
|
|
34778
35997
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
34779
35998
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
34780
35999
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -35347,7 +36566,7 @@ class BacktestUtils {
|
|
|
35347
36566
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35348
36567
|
* @returns Effective entry price, or null if no active position
|
|
35349
36568
|
*/
|
|
35350
|
-
this.
|
|
36569
|
+
this.getPositionEffectivePrice = async (symbol, context) => {
|
|
35351
36570
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
35352
36571
|
symbol,
|
|
35353
36572
|
context,
|
|
@@ -35363,7 +36582,7 @@ class BacktestUtils {
|
|
|
35363
36582
|
actions &&
|
|
35364
36583
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
35365
36584
|
}
|
|
35366
|
-
return await bt.strategyCoreService.
|
|
36585
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
35367
36586
|
};
|
|
35368
36587
|
/**
|
|
35369
36588
|
* Returns the total number of base-asset units currently held in the position.
|
|
@@ -35581,6 +36800,230 @@ class BacktestUtils {
|
|
|
35581
36800
|
}
|
|
35582
36801
|
return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
|
|
35583
36802
|
};
|
|
36803
|
+
/**
|
|
36804
|
+
* Returns the original estimated duration for the current pending signal.
|
|
36805
|
+
*
|
|
36806
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
36807
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
36808
|
+
*
|
|
36809
|
+
* Returns null if no pending signal exists.
|
|
36810
|
+
*
|
|
36811
|
+
* @param symbol - Trading pair symbol
|
|
36812
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36813
|
+
* @returns Estimated duration in minutes, or null if no active position
|
|
36814
|
+
*/
|
|
36815
|
+
this.getPositionEstimateMinutes = async (symbol, context) => {
|
|
36816
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
|
|
36817
|
+
symbol,
|
|
36818
|
+
context,
|
|
36819
|
+
});
|
|
36820
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36821
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36822
|
+
{
|
|
36823
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36824
|
+
riskName &&
|
|
36825
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36826
|
+
riskList &&
|
|
36827
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
36828
|
+
actions &&
|
|
36829
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
36830
|
+
}
|
|
36831
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(true, symbol, context);
|
|
36832
|
+
};
|
|
36833
|
+
/**
|
|
36834
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
36835
|
+
*
|
|
36836
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
36837
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
36838
|
+
*
|
|
36839
|
+
* Returns null if no pending signal exists.
|
|
36840
|
+
*
|
|
36841
|
+
* @param symbol - Trading pair symbol
|
|
36842
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36843
|
+
* @returns Remaining minutes (≥ 0), or null if no active position
|
|
36844
|
+
*/
|
|
36845
|
+
this.getPositionCountdownMinutes = async (symbol, context) => {
|
|
36846
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
|
|
36847
|
+
symbol,
|
|
36848
|
+
context,
|
|
36849
|
+
});
|
|
36850
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36851
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36852
|
+
{
|
|
36853
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36854
|
+
riskName &&
|
|
36855
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36856
|
+
riskList &&
|
|
36857
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
36858
|
+
actions &&
|
|
36859
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
36860
|
+
}
|
|
36861
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
|
|
36862
|
+
};
|
|
36863
|
+
/**
|
|
36864
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
36865
|
+
*
|
|
36866
|
+
* Returns null if no pending signal exists.
|
|
36867
|
+
*
|
|
36868
|
+
* @param symbol - Trading pair symbol
|
|
36869
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36870
|
+
* @returns price or null if no active position
|
|
36871
|
+
*/
|
|
36872
|
+
this.getPositionHighestProfitPrice = async (symbol, context) => {
|
|
36873
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
|
|
36874
|
+
symbol,
|
|
36875
|
+
context,
|
|
36876
|
+
});
|
|
36877
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36878
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36879
|
+
{
|
|
36880
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36881
|
+
riskName &&
|
|
36882
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36883
|
+
riskList &&
|
|
36884
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
36885
|
+
actions &&
|
|
36886
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
36887
|
+
}
|
|
36888
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(true, symbol, context);
|
|
36889
|
+
};
|
|
36890
|
+
/**
|
|
36891
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
36892
|
+
*
|
|
36893
|
+
* Returns null if no pending signal exists.
|
|
36894
|
+
*
|
|
36895
|
+
* @param symbol - Trading pair symbol
|
|
36896
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36897
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
36898
|
+
*/
|
|
36899
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context) => {
|
|
36900
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
|
|
36901
|
+
symbol,
|
|
36902
|
+
context,
|
|
36903
|
+
});
|
|
36904
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36905
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36906
|
+
{
|
|
36907
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36908
|
+
riskName &&
|
|
36909
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36910
|
+
riskList &&
|
|
36911
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
36912
|
+
actions &&
|
|
36913
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
36914
|
+
}
|
|
36915
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(true, symbol, context);
|
|
36916
|
+
};
|
|
36917
|
+
/**
|
|
36918
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
36919
|
+
*
|
|
36920
|
+
* Returns null if no pending signal exists.
|
|
36921
|
+
*
|
|
36922
|
+
* @param symbol - Trading pair symbol
|
|
36923
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36924
|
+
* @returns PnL percentage or null if no active position
|
|
36925
|
+
*/
|
|
36926
|
+
this.getPositionHighestPnlPercentage = async (symbol, context) => {
|
|
36927
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
|
|
36928
|
+
symbol,
|
|
36929
|
+
context,
|
|
36930
|
+
});
|
|
36931
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36932
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36933
|
+
{
|
|
36934
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36935
|
+
riskName &&
|
|
36936
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36937
|
+
riskList &&
|
|
36938
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
36939
|
+
actions &&
|
|
36940
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
36941
|
+
}
|
|
36942
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(true, symbol, context);
|
|
36943
|
+
};
|
|
36944
|
+
/**
|
|
36945
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
36946
|
+
*
|
|
36947
|
+
* Returns null if no pending signal exists.
|
|
36948
|
+
*
|
|
36949
|
+
* @param symbol - Trading pair symbol
|
|
36950
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36951
|
+
* @returns PnL cost or null if no active position
|
|
36952
|
+
*/
|
|
36953
|
+
this.getPositionHighestPnlCost = async (symbol, context) => {
|
|
36954
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
|
|
36955
|
+
symbol,
|
|
36956
|
+
context,
|
|
36957
|
+
});
|
|
36958
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36959
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36960
|
+
{
|
|
36961
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36962
|
+
riskName &&
|
|
36963
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36964
|
+
riskList &&
|
|
36965
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
36966
|
+
actions &&
|
|
36967
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
36968
|
+
}
|
|
36969
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(true, symbol, context);
|
|
36970
|
+
};
|
|
36971
|
+
/**
|
|
36972
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
36973
|
+
*
|
|
36974
|
+
* @param symbol - Trading pair symbol
|
|
36975
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36976
|
+
* @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
|
|
36977
|
+
*/
|
|
36978
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context) => {
|
|
36979
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
|
|
36980
|
+
symbol,
|
|
36981
|
+
context,
|
|
36982
|
+
});
|
|
36983
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
36984
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
36985
|
+
{
|
|
36986
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36987
|
+
riskName &&
|
|
36988
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
36989
|
+
riskList &&
|
|
36990
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
36991
|
+
actions &&
|
|
36992
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
36993
|
+
}
|
|
36994
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(true, symbol, context);
|
|
36995
|
+
};
|
|
36996
|
+
/**
|
|
36997
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
36998
|
+
*
|
|
36999
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
37000
|
+
* Zero when called at the exact moment the peak was set.
|
|
37001
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
37002
|
+
*
|
|
37003
|
+
* Returns null if no pending signal exists.
|
|
37004
|
+
*
|
|
37005
|
+
* @param symbol - Trading pair symbol
|
|
37006
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
37007
|
+
* @returns Drawdown duration in minutes, or null if no active position
|
|
37008
|
+
*/
|
|
37009
|
+
this.getPositionDrawdownMinutes = async (symbol, context) => {
|
|
37010
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
|
|
37011
|
+
symbol,
|
|
37012
|
+
context,
|
|
37013
|
+
});
|
|
37014
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37015
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37016
|
+
{
|
|
37017
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37018
|
+
riskName &&
|
|
37019
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37020
|
+
riskList &&
|
|
37021
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
37022
|
+
actions &&
|
|
37023
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
37024
|
+
}
|
|
37025
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
|
|
37026
|
+
};
|
|
35584
37027
|
/**
|
|
35585
37028
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
35586
37029
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -36142,7 +37585,7 @@ class BacktestUtils {
|
|
|
36142
37585
|
if (!signal) {
|
|
36143
37586
|
return false;
|
|
36144
37587
|
}
|
|
36145
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37588
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36146
37589
|
if (effectivePriceOpen === null) {
|
|
36147
37590
|
return false;
|
|
36148
37591
|
}
|
|
@@ -36227,7 +37670,7 @@ class BacktestUtils {
|
|
|
36227
37670
|
if (!signal) {
|
|
36228
37671
|
return false;
|
|
36229
37672
|
}
|
|
36230
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37673
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36231
37674
|
if (effectivePriceOpen === null) {
|
|
36232
37675
|
return false;
|
|
36233
37676
|
}
|
|
@@ -36280,7 +37723,7 @@ class BacktestUtils {
|
|
|
36280
37723
|
if (!signal) {
|
|
36281
37724
|
return false;
|
|
36282
37725
|
}
|
|
36283
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37726
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36284
37727
|
if (effectivePriceOpen === null) {
|
|
36285
37728
|
return false;
|
|
36286
37729
|
}
|
|
@@ -36334,7 +37777,7 @@ class BacktestUtils {
|
|
|
36334
37777
|
if (!signal) {
|
|
36335
37778
|
return false;
|
|
36336
37779
|
}
|
|
36337
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37780
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36338
37781
|
if (effectivePriceOpen === null) {
|
|
36339
37782
|
return false;
|
|
36340
37783
|
}
|
|
@@ -36396,7 +37839,7 @@ class BacktestUtils {
|
|
|
36396
37839
|
if (!signal) {
|
|
36397
37840
|
return false;
|
|
36398
37841
|
}
|
|
36399
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37842
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36400
37843
|
if (effectivePriceOpen === null) {
|
|
36401
37844
|
return false;
|
|
36402
37845
|
}
|
|
@@ -36684,7 +38127,7 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
|
|
|
36684
38127
|
const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
|
|
36685
38128
|
const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
|
|
36686
38129
|
const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
|
|
36687
|
-
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.
|
|
38130
|
+
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionEffectivePrice";
|
|
36688
38131
|
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
|
|
36689
38132
|
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
|
|
36690
38133
|
const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
|
|
@@ -36692,6 +38135,14 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
|
36692
38135
|
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
36693
38136
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
36694
38137
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
|
|
38138
|
+
const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
|
|
38139
|
+
const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
|
|
38140
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
|
|
38141
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
|
|
38142
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
|
|
38143
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
|
|
38144
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
|
|
38145
|
+
const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
|
|
36695
38146
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
36696
38147
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
36697
38148
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -37295,7 +38746,7 @@ class LiveUtils {
|
|
|
37295
38746
|
* @param context - Execution context with strategyName and exchangeName
|
|
37296
38747
|
* @returns Effective entry price, or null if no active position
|
|
37297
38748
|
*/
|
|
37298
|
-
this.
|
|
38749
|
+
this.getPositionEffectivePrice = async (symbol, context) => {
|
|
37299
38750
|
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
37300
38751
|
symbol,
|
|
37301
38752
|
context,
|
|
@@ -37311,7 +38762,7 @@ class LiveUtils {
|
|
|
37311
38762
|
actions &&
|
|
37312
38763
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
37313
38764
|
}
|
|
37314
|
-
return await bt.strategyCoreService.
|
|
38765
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
37315
38766
|
strategyName: context.strategyName,
|
|
37316
38767
|
exchangeName: context.exchangeName,
|
|
37317
38768
|
frameName: "",
|
|
@@ -37561,6 +39012,262 @@ class LiveUtils {
|
|
|
37561
39012
|
frameName: "",
|
|
37562
39013
|
});
|
|
37563
39014
|
};
|
|
39015
|
+
/**
|
|
39016
|
+
* Returns the original estimated duration for the current pending signal.
|
|
39017
|
+
*
|
|
39018
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
39019
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
39020
|
+
*
|
|
39021
|
+
* Returns null if no pending signal exists.
|
|
39022
|
+
*
|
|
39023
|
+
* @param symbol - Trading pair symbol
|
|
39024
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39025
|
+
* @returns Estimated duration in minutes, or null if no active position
|
|
39026
|
+
*/
|
|
39027
|
+
this.getPositionEstimateMinutes = async (symbol, context) => {
|
|
39028
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
|
|
39029
|
+
symbol,
|
|
39030
|
+
context,
|
|
39031
|
+
});
|
|
39032
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39033
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39034
|
+
{
|
|
39035
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39036
|
+
riskName &&
|
|
39037
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39038
|
+
riskList &&
|
|
39039
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
39040
|
+
actions &&
|
|
39041
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
39042
|
+
}
|
|
39043
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(false, symbol, {
|
|
39044
|
+
strategyName: context.strategyName,
|
|
39045
|
+
exchangeName: context.exchangeName,
|
|
39046
|
+
frameName: "",
|
|
39047
|
+
});
|
|
39048
|
+
};
|
|
39049
|
+
/**
|
|
39050
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
39051
|
+
*
|
|
39052
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
39053
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
39054
|
+
*
|
|
39055
|
+
* Returns null if no pending signal exists.
|
|
39056
|
+
*
|
|
39057
|
+
* @param symbol - Trading pair symbol
|
|
39058
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39059
|
+
* @returns Remaining minutes (≥ 0), or null if no active position
|
|
39060
|
+
*/
|
|
39061
|
+
this.getPositionCountdownMinutes = async (symbol, context) => {
|
|
39062
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
|
|
39063
|
+
symbol,
|
|
39064
|
+
context,
|
|
39065
|
+
});
|
|
39066
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39067
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39068
|
+
{
|
|
39069
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39070
|
+
riskName &&
|
|
39071
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39072
|
+
riskList &&
|
|
39073
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
39074
|
+
actions &&
|
|
39075
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
39076
|
+
}
|
|
39077
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(false, symbol, {
|
|
39078
|
+
strategyName: context.strategyName,
|
|
39079
|
+
exchangeName: context.exchangeName,
|
|
39080
|
+
frameName: "",
|
|
39081
|
+
});
|
|
39082
|
+
};
|
|
39083
|
+
/**
|
|
39084
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
39085
|
+
*
|
|
39086
|
+
* Returns null if no pending signal exists.
|
|
39087
|
+
*
|
|
39088
|
+
* @param symbol - Trading pair symbol
|
|
39089
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39090
|
+
* @returns price or null if no active position
|
|
39091
|
+
*/
|
|
39092
|
+
this.getPositionHighestProfitPrice = async (symbol, context) => {
|
|
39093
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
|
|
39094
|
+
symbol,
|
|
39095
|
+
context,
|
|
39096
|
+
});
|
|
39097
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39098
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39099
|
+
{
|
|
39100
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39101
|
+
riskName &&
|
|
39102
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39103
|
+
riskList &&
|
|
39104
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
39105
|
+
actions &&
|
|
39106
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
39107
|
+
}
|
|
39108
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(false, symbol, {
|
|
39109
|
+
strategyName: context.strategyName,
|
|
39110
|
+
exchangeName: context.exchangeName,
|
|
39111
|
+
frameName: "",
|
|
39112
|
+
});
|
|
39113
|
+
};
|
|
39114
|
+
/**
|
|
39115
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
39116
|
+
*
|
|
39117
|
+
* Returns null if no pending signal exists.
|
|
39118
|
+
*
|
|
39119
|
+
* @param symbol - Trading pair symbol
|
|
39120
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39121
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
39122
|
+
*/
|
|
39123
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context) => {
|
|
39124
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
|
|
39125
|
+
symbol,
|
|
39126
|
+
context,
|
|
39127
|
+
});
|
|
39128
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39129
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39130
|
+
{
|
|
39131
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39132
|
+
riskName &&
|
|
39133
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39134
|
+
riskList &&
|
|
39135
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
39136
|
+
actions &&
|
|
39137
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
39138
|
+
}
|
|
39139
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(false, symbol, {
|
|
39140
|
+
strategyName: context.strategyName,
|
|
39141
|
+
exchangeName: context.exchangeName,
|
|
39142
|
+
frameName: "",
|
|
39143
|
+
});
|
|
39144
|
+
};
|
|
39145
|
+
/**
|
|
39146
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
39147
|
+
*
|
|
39148
|
+
* Returns null if no pending signal exists.
|
|
39149
|
+
*
|
|
39150
|
+
* @param symbol - Trading pair symbol
|
|
39151
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39152
|
+
* @returns PnL percentage or null if no active position
|
|
39153
|
+
*/
|
|
39154
|
+
this.getPositionHighestPnlPercentage = async (symbol, context) => {
|
|
39155
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
|
|
39156
|
+
symbol,
|
|
39157
|
+
context,
|
|
39158
|
+
});
|
|
39159
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39160
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39161
|
+
{
|
|
39162
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39163
|
+
riskName &&
|
|
39164
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39165
|
+
riskList &&
|
|
39166
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
39167
|
+
actions &&
|
|
39168
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
39169
|
+
}
|
|
39170
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(false, symbol, {
|
|
39171
|
+
strategyName: context.strategyName,
|
|
39172
|
+
exchangeName: context.exchangeName,
|
|
39173
|
+
frameName: "",
|
|
39174
|
+
});
|
|
39175
|
+
};
|
|
39176
|
+
/**
|
|
39177
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
39178
|
+
*
|
|
39179
|
+
* Returns null if no pending signal exists.
|
|
39180
|
+
*
|
|
39181
|
+
* @param symbol - Trading pair symbol
|
|
39182
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39183
|
+
* @returns PnL cost or null if no active position
|
|
39184
|
+
*/
|
|
39185
|
+
this.getPositionHighestPnlCost = async (symbol, context) => {
|
|
39186
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
|
|
39187
|
+
symbol,
|
|
39188
|
+
context,
|
|
39189
|
+
});
|
|
39190
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39191
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39192
|
+
{
|
|
39193
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39194
|
+
riskName &&
|
|
39195
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39196
|
+
riskList &&
|
|
39197
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
39198
|
+
actions &&
|
|
39199
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
39200
|
+
}
|
|
39201
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(false, symbol, {
|
|
39202
|
+
strategyName: context.strategyName,
|
|
39203
|
+
exchangeName: context.exchangeName,
|
|
39204
|
+
frameName: "",
|
|
39205
|
+
});
|
|
39206
|
+
};
|
|
39207
|
+
/**
|
|
39208
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
39209
|
+
*
|
|
39210
|
+
* @param symbol - Trading pair symbol
|
|
39211
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39212
|
+
* @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
|
|
39213
|
+
*/
|
|
39214
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context) => {
|
|
39215
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
|
|
39216
|
+
symbol,
|
|
39217
|
+
context,
|
|
39218
|
+
});
|
|
39219
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39220
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39221
|
+
{
|
|
39222
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39223
|
+
riskName &&
|
|
39224
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39225
|
+
riskList &&
|
|
39226
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
39227
|
+
actions &&
|
|
39228
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
39229
|
+
}
|
|
39230
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(false, symbol, {
|
|
39231
|
+
strategyName: context.strategyName,
|
|
39232
|
+
exchangeName: context.exchangeName,
|
|
39233
|
+
frameName: "",
|
|
39234
|
+
});
|
|
39235
|
+
};
|
|
39236
|
+
/**
|
|
39237
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
39238
|
+
*
|
|
39239
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
39240
|
+
* Zero when called at the exact moment the peak was set.
|
|
39241
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
39242
|
+
*
|
|
39243
|
+
* Returns null if no pending signal exists.
|
|
39244
|
+
*
|
|
39245
|
+
* @param symbol - Trading pair symbol
|
|
39246
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39247
|
+
* @returns Drawdown duration in minutes, or null if no active position
|
|
39248
|
+
*/
|
|
39249
|
+
this.getPositionDrawdownMinutes = async (symbol, context) => {
|
|
39250
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
|
|
39251
|
+
symbol,
|
|
39252
|
+
context,
|
|
39253
|
+
});
|
|
39254
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39255
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39256
|
+
{
|
|
39257
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39258
|
+
riskName &&
|
|
39259
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39260
|
+
riskList &&
|
|
39261
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
39262
|
+
actions &&
|
|
39263
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
39264
|
+
}
|
|
39265
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(false, symbol, {
|
|
39266
|
+
strategyName: context.strategyName,
|
|
39267
|
+
exchangeName: context.exchangeName,
|
|
39268
|
+
frameName: "",
|
|
39269
|
+
});
|
|
39270
|
+
};
|
|
37564
39271
|
/**
|
|
37565
39272
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
37566
39273
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38215,7 +39922,7 @@ class LiveUtils {
|
|
|
38215
39922
|
if (!signal) {
|
|
38216
39923
|
return false;
|
|
38217
39924
|
}
|
|
38218
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
39925
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38219
39926
|
strategyName: context.strategyName,
|
|
38220
39927
|
exchangeName: context.exchangeName,
|
|
38221
39928
|
frameName: "",
|
|
@@ -38315,7 +40022,7 @@ class LiveUtils {
|
|
|
38315
40022
|
if (!signal) {
|
|
38316
40023
|
return false;
|
|
38317
40024
|
}
|
|
38318
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40025
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38319
40026
|
strategyName: context.strategyName,
|
|
38320
40027
|
exchangeName: context.exchangeName,
|
|
38321
40028
|
frameName: "",
|
|
@@ -38384,7 +40091,7 @@ class LiveUtils {
|
|
|
38384
40091
|
if (!signal) {
|
|
38385
40092
|
return false;
|
|
38386
40093
|
}
|
|
38387
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40094
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38388
40095
|
strategyName: context.strategyName,
|
|
38389
40096
|
exchangeName: context.exchangeName,
|
|
38390
40097
|
frameName: "",
|
|
@@ -38454,7 +40161,7 @@ class LiveUtils {
|
|
|
38454
40161
|
if (!signal) {
|
|
38455
40162
|
return false;
|
|
38456
40163
|
}
|
|
38457
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40164
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38458
40165
|
strategyName: context.strategyName,
|
|
38459
40166
|
exchangeName: context.exchangeName,
|
|
38460
40167
|
frameName: "",
|
|
@@ -38532,7 +40239,7 @@ class LiveUtils {
|
|
|
38532
40239
|
if (!signal) {
|
|
38533
40240
|
return false;
|
|
38534
40241
|
}
|
|
38535
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40242
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38536
40243
|
strategyName: context.strategyName,
|
|
38537
40244
|
exchangeName: context.exchangeName,
|
|
38538
40245
|
frameName: "",
|
|
@@ -41956,6 +43663,98 @@ class PartialUtils {
|
|
|
41956
43663
|
*/
|
|
41957
43664
|
const Partial = new PartialUtils();
|
|
41958
43665
|
|
|
43666
|
+
const HIGHEST_PROFIT_METHOD_NAME_GET_DATA = "HighestProfitUtils.getData";
|
|
43667
|
+
const HIGHEST_PROFIT_METHOD_NAME_GET_REPORT = "HighestProfitUtils.getReport";
|
|
43668
|
+
const HIGHEST_PROFIT_METHOD_NAME_DUMP = "HighestProfitUtils.dump";
|
|
43669
|
+
/**
|
|
43670
|
+
* Utility class for accessing highest profit reports and statistics.
|
|
43671
|
+
*
|
|
43672
|
+
* Provides static-like methods (via singleton instance) to retrieve data
|
|
43673
|
+
* accumulated by HighestProfitMarkdownService from highestProfitSubject events.
|
|
43674
|
+
*
|
|
43675
|
+
* @example
|
|
43676
|
+
* ```typescript
|
|
43677
|
+
* import { HighestProfit } from "backtest-kit";
|
|
43678
|
+
*
|
|
43679
|
+
* const stats = await HighestProfit.getData("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43680
|
+
* const report = await HighestProfit.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43681
|
+
* await HighestProfit.dump("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43682
|
+
* ```
|
|
43683
|
+
*/
|
|
43684
|
+
class HighestProfitUtils {
|
|
43685
|
+
constructor() {
|
|
43686
|
+
/**
|
|
43687
|
+
* Retrieves statistical data from accumulated highest profit events.
|
|
43688
|
+
*
|
|
43689
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43690
|
+
* @param context - Execution context
|
|
43691
|
+
* @param backtest - Whether to query backtest data
|
|
43692
|
+
* @returns Promise resolving to HighestProfitStatisticsModel
|
|
43693
|
+
*/
|
|
43694
|
+
this.getData = async (symbol, context, backtest = false) => {
|
|
43695
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
43696
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43697
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43698
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43699
|
+
{
|
|
43700
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43701
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43702
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
|
|
43703
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
|
|
43704
|
+
}
|
|
43705
|
+
return await bt.highestProfitMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
43706
|
+
};
|
|
43707
|
+
/**
|
|
43708
|
+
* Generates a markdown report with all highest profit events for a symbol-strategy pair.
|
|
43709
|
+
*
|
|
43710
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43711
|
+
* @param context - Execution context
|
|
43712
|
+
* @param backtest - Whether to query backtest data
|
|
43713
|
+
* @param columns - Optional column configuration
|
|
43714
|
+
* @returns Promise resolving to markdown formatted report string
|
|
43715
|
+
*/
|
|
43716
|
+
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
43717
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
43718
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43719
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43720
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43721
|
+
{
|
|
43722
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43723
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43724
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
|
|
43725
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
|
|
43726
|
+
}
|
|
43727
|
+
return await bt.highestProfitMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
|
|
43728
|
+
};
|
|
43729
|
+
/**
|
|
43730
|
+
* Generates and saves a markdown report to file.
|
|
43731
|
+
*
|
|
43732
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43733
|
+
* @param context - Execution context
|
|
43734
|
+
* @param backtest - Whether to query backtest data
|
|
43735
|
+
* @param path - Output directory path (default: "./dump/highest_profit")
|
|
43736
|
+
* @param columns - Optional column configuration
|
|
43737
|
+
*/
|
|
43738
|
+
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
43739
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
43740
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43741
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43742
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43743
|
+
{
|
|
43744
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43745
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43746
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
|
|
43747
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
|
|
43748
|
+
}
|
|
43749
|
+
await bt.highestProfitMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
|
|
43750
|
+
};
|
|
43751
|
+
}
|
|
43752
|
+
}
|
|
43753
|
+
/**
|
|
43754
|
+
* Global singleton instance of HighestProfitUtils.
|
|
43755
|
+
*/
|
|
43756
|
+
const HighestProfit = new HighestProfitUtils();
|
|
43757
|
+
|
|
41959
43758
|
/**
|
|
41960
43759
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
41961
43760
|
*
|
|
@@ -46423,4 +48222,4 @@ const percentValue = (yesterdayValue, todayValue) => {
|
|
|
46423
48222
|
return yesterdayValue / todayValue - 1;
|
|
46424
48223
|
};
|
|
46425
48224
|
|
|
46426
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal,
|
|
48225
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, HighestProfit, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
|