backtest-kit 5.5.3 → 5.6.1
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 +1999 -173
- package/build/index.mjs +1987 -173
- package/package.json +1 -1
- package/types.d.ts +1141 -179
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,93 @@ 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: "pnl",
|
|
18487
|
+
label: "PNL (net)",
|
|
18488
|
+
format: (data) => {
|
|
18489
|
+
const pnlPercentage = data.pnl.pnlPercentage;
|
|
18490
|
+
return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
|
|
18491
|
+
},
|
|
18492
|
+
isVisible: () => true,
|
|
18493
|
+
},
|
|
18494
|
+
{
|
|
18495
|
+
key: "pnlCost",
|
|
18496
|
+
label: "PNL (USD)",
|
|
18497
|
+
format: (data) => `${data.pnl.pnlCost > 0 ? "+" : ""}${data.pnl.pnlCost.toFixed(2)} USD`,
|
|
18498
|
+
isVisible: () => true,
|
|
18499
|
+
},
|
|
18500
|
+
{
|
|
18501
|
+
key: "currentPrice",
|
|
18502
|
+
label: "Record Price",
|
|
18503
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
18504
|
+
isVisible: () => true,
|
|
18505
|
+
},
|
|
18506
|
+
{
|
|
18507
|
+
key: "priceOpen",
|
|
18508
|
+
label: "Entry Price",
|
|
18509
|
+
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
18510
|
+
isVisible: () => true,
|
|
18511
|
+
},
|
|
18512
|
+
{
|
|
18513
|
+
key: "priceTakeProfit",
|
|
18514
|
+
label: "Take Profit",
|
|
18515
|
+
format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
|
|
18516
|
+
isVisible: () => true,
|
|
18517
|
+
},
|
|
18518
|
+
{
|
|
18519
|
+
key: "priceStopLoss",
|
|
18520
|
+
label: "Stop Loss",
|
|
18521
|
+
format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
|
|
18522
|
+
isVisible: () => true,
|
|
18523
|
+
},
|
|
18524
|
+
{
|
|
18525
|
+
key: "timestamp",
|
|
18526
|
+
label: "Timestamp",
|
|
18527
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
18528
|
+
isVisible: () => true,
|
|
18529
|
+
},
|
|
18530
|
+
{
|
|
18531
|
+
key: "mode",
|
|
18532
|
+
label: "Mode",
|
|
18533
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
18534
|
+
isVisible: () => true,
|
|
18535
|
+
},
|
|
18536
|
+
];
|
|
18537
|
+
|
|
17887
18538
|
/**
|
|
17888
18539
|
* Column configuration for walker strategy comparison table in markdown reports.
|
|
17889
18540
|
*
|
|
@@ -18101,6 +18752,8 @@ const COLUMN_CONFIG = {
|
|
|
18101
18752
|
strategy_columns,
|
|
18102
18753
|
/** Columns for signal sync lifecycle events (signal-open, signal-close) */
|
|
18103
18754
|
sync_columns,
|
|
18755
|
+
/** Columns for highest profit milestone tracking events */
|
|
18756
|
+
highest_profit_columns,
|
|
18104
18757
|
/** Walker: PnL summary columns */
|
|
18105
18758
|
walker_pnl_columns,
|
|
18106
18759
|
/** Walker: strategy-level summary columns */
|
|
@@ -18141,6 +18794,7 @@ const WILDCARD_TARGET$1 = {
|
|
|
18141
18794
|
schedule: true,
|
|
18142
18795
|
walker: true,
|
|
18143
18796
|
sync: true,
|
|
18797
|
+
highest_profit: true,
|
|
18144
18798
|
};
|
|
18145
18799
|
/**
|
|
18146
18800
|
* JSONL-based markdown adapter with append-only writes.
|
|
@@ -18368,7 +19022,7 @@ class MarkdownUtils {
|
|
|
18368
19022
|
*
|
|
18369
19023
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
18370
19024
|
*/
|
|
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) => {
|
|
19025
|
+
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
19026
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
|
|
18373
19027
|
backtest: bt$1,
|
|
18374
19028
|
breakeven,
|
|
@@ -18381,6 +19035,7 @@ class MarkdownUtils {
|
|
|
18381
19035
|
schedule,
|
|
18382
19036
|
walker,
|
|
18383
19037
|
sync,
|
|
19038
|
+
highest_profit,
|
|
18384
19039
|
});
|
|
18385
19040
|
const unList = [];
|
|
18386
19041
|
if (bt$1) {
|
|
@@ -18416,6 +19071,9 @@ class MarkdownUtils {
|
|
|
18416
19071
|
if (sync) {
|
|
18417
19072
|
unList.push(bt.syncMarkdownService.subscribe());
|
|
18418
19073
|
}
|
|
19074
|
+
if (highest_profit) {
|
|
19075
|
+
unList.push(bt.highestProfitMarkdownService.subscribe());
|
|
19076
|
+
}
|
|
18419
19077
|
return compose(...unList.map((un) => () => void un()));
|
|
18420
19078
|
};
|
|
18421
19079
|
/**
|
|
@@ -18455,7 +19113,7 @@ class MarkdownUtils {
|
|
|
18455
19113
|
* Markdown.disable();
|
|
18456
19114
|
* ```
|
|
18457
19115
|
*/
|
|
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) => {
|
|
19116
|
+
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
19117
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
|
|
18460
19118
|
backtest: bt$1,
|
|
18461
19119
|
breakeven,
|
|
@@ -18468,6 +19126,7 @@ class MarkdownUtils {
|
|
|
18468
19126
|
schedule,
|
|
18469
19127
|
walker,
|
|
18470
19128
|
sync,
|
|
19129
|
+
highest_profit,
|
|
18471
19130
|
});
|
|
18472
19131
|
if (bt$1) {
|
|
18473
19132
|
bt.backtestMarkdownService.unsubscribe();
|
|
@@ -18502,6 +19161,9 @@ class MarkdownUtils {
|
|
|
18502
19161
|
if (sync) {
|
|
18503
19162
|
bt.syncMarkdownService.unsubscribe();
|
|
18504
19163
|
}
|
|
19164
|
+
if (highest_profit) {
|
|
19165
|
+
bt.highestProfitMarkdownService.unsubscribe();
|
|
19166
|
+
}
|
|
18505
19167
|
};
|
|
18506
19168
|
}
|
|
18507
19169
|
}
|
|
@@ -18608,7 +19270,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
18608
19270
|
* @param backtest - Whether running in backtest mode
|
|
18609
19271
|
* @returns Unique string key for memoization
|
|
18610
19272
|
*/
|
|
18611
|
-
const CREATE_KEY_FN$
|
|
19273
|
+
const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
18612
19274
|
const parts = [symbol, strategyName, exchangeName];
|
|
18613
19275
|
if (frameName)
|
|
18614
19276
|
parts.push(frameName);
|
|
@@ -18625,7 +19287,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
18625
19287
|
* @param timestamp - Unix timestamp in milliseconds
|
|
18626
19288
|
* @returns Filename string
|
|
18627
19289
|
*/
|
|
18628
|
-
const CREATE_FILE_NAME_FN$
|
|
19290
|
+
const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
18629
19291
|
const parts = [symbol, strategyName, exchangeName];
|
|
18630
19292
|
if (frameName) {
|
|
18631
19293
|
parts.push(frameName);
|
|
@@ -18654,12 +19316,12 @@ function isUnsafe$3(value) {
|
|
|
18654
19316
|
return false;
|
|
18655
19317
|
}
|
|
18656
19318
|
/** Maximum number of signals to store in backtest reports */
|
|
18657
|
-
const MAX_EVENTS$
|
|
19319
|
+
const MAX_EVENTS$a = 250;
|
|
18658
19320
|
/**
|
|
18659
19321
|
* Storage class for accumulating closed signals per strategy.
|
|
18660
19322
|
* Maintains a list of all closed signals and provides methods to generate reports.
|
|
18661
19323
|
*/
|
|
18662
|
-
let ReportStorage$
|
|
19324
|
+
let ReportStorage$9 = class ReportStorage {
|
|
18663
19325
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
18664
19326
|
this.symbol = symbol;
|
|
18665
19327
|
this.strategyName = strategyName;
|
|
@@ -18676,7 +19338,7 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
18676
19338
|
addSignal(data) {
|
|
18677
19339
|
this._signalList.unshift(data);
|
|
18678
19340
|
// Trim queue if exceeded MAX_EVENTS
|
|
18679
|
-
if (this._signalList.length > MAX_EVENTS$
|
|
19341
|
+
if (this._signalList.length > MAX_EVENTS$a) {
|
|
18680
19342
|
this._signalList.pop();
|
|
18681
19343
|
}
|
|
18682
19344
|
}
|
|
@@ -18800,7 +19462,7 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
18800
19462
|
async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
18801
19463
|
const markdown = await this.getReport(strategyName, columns);
|
|
18802
19464
|
const timestamp = getContextTimestamp();
|
|
18803
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
19465
|
+
const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
18804
19466
|
await Markdown.writeData("backtest", markdown, {
|
|
18805
19467
|
path,
|
|
18806
19468
|
file: filename,
|
|
@@ -18847,7 +19509,7 @@ class BacktestMarkdownService {
|
|
|
18847
19509
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
18848
19510
|
* Each combination gets its own isolated storage instance.
|
|
18849
19511
|
*/
|
|
18850
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
19512
|
+
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
19513
|
/**
|
|
18852
19514
|
* Processes tick events and accumulates closed signals.
|
|
18853
19515
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -19004,7 +19666,7 @@ class BacktestMarkdownService {
|
|
|
19004
19666
|
payload,
|
|
19005
19667
|
});
|
|
19006
19668
|
if (payload) {
|
|
19007
|
-
const key = CREATE_KEY_FN$
|
|
19669
|
+
const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19008
19670
|
this.getStorage.clear(key);
|
|
19009
19671
|
}
|
|
19010
19672
|
else {
|
|
@@ -19066,7 +19728,7 @@ class BacktestMarkdownService {
|
|
|
19066
19728
|
* @param backtest - Whether running in backtest mode
|
|
19067
19729
|
* @returns Unique string key for memoization
|
|
19068
19730
|
*/
|
|
19069
|
-
const CREATE_KEY_FN$
|
|
19731
|
+
const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19070
19732
|
const parts = [symbol, strategyName, exchangeName];
|
|
19071
19733
|
if (frameName)
|
|
19072
19734
|
parts.push(frameName);
|
|
@@ -19083,7 +19745,7 @@ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19083
19745
|
* @param timestamp - Unix timestamp in milliseconds
|
|
19084
19746
|
* @returns Filename string
|
|
19085
19747
|
*/
|
|
19086
|
-
const CREATE_FILE_NAME_FN$
|
|
19748
|
+
const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19087
19749
|
const parts = [symbol, strategyName, exchangeName];
|
|
19088
19750
|
if (frameName) {
|
|
19089
19751
|
parts.push(frameName);
|
|
@@ -19112,12 +19774,12 @@ function isUnsafe$2(value) {
|
|
|
19112
19774
|
return false;
|
|
19113
19775
|
}
|
|
19114
19776
|
/** Maximum number of events to store in live trading reports */
|
|
19115
|
-
const MAX_EVENTS$
|
|
19777
|
+
const MAX_EVENTS$9 = 250;
|
|
19116
19778
|
/**
|
|
19117
19779
|
* Storage class for accumulating all tick events per strategy.
|
|
19118
19780
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
19119
19781
|
*/
|
|
19120
|
-
let ReportStorage$
|
|
19782
|
+
let ReportStorage$8 = class ReportStorage {
|
|
19121
19783
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19122
19784
|
this.symbol = symbol;
|
|
19123
19785
|
this.strategyName = strategyName;
|
|
@@ -19149,7 +19811,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19149
19811
|
}
|
|
19150
19812
|
{
|
|
19151
19813
|
this._eventList.unshift(newEvent);
|
|
19152
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19814
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19153
19815
|
this._eventList.pop();
|
|
19154
19816
|
}
|
|
19155
19817
|
}
|
|
@@ -19179,7 +19841,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19179
19841
|
scheduledAt: data.signal.scheduledAt,
|
|
19180
19842
|
});
|
|
19181
19843
|
// Trim queue if exceeded MAX_EVENTS
|
|
19182
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19844
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19183
19845
|
this._eventList.pop();
|
|
19184
19846
|
}
|
|
19185
19847
|
}
|
|
@@ -19223,7 +19885,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19223
19885
|
// If no previous active event found, add new event
|
|
19224
19886
|
this._eventList.unshift(newEvent);
|
|
19225
19887
|
// Trim queue if exceeded MAX_EVENTS
|
|
19226
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19888
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19227
19889
|
this._eventList.pop();
|
|
19228
19890
|
}
|
|
19229
19891
|
}
|
|
@@ -19260,7 +19922,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19260
19922
|
};
|
|
19261
19923
|
this._eventList.unshift(newEvent);
|
|
19262
19924
|
// Trim queue if exceeded MAX_EVENTS
|
|
19263
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19925
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19264
19926
|
this._eventList.pop();
|
|
19265
19927
|
}
|
|
19266
19928
|
}
|
|
@@ -19288,7 +19950,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19288
19950
|
scheduledAt: data.signal.scheduledAt,
|
|
19289
19951
|
});
|
|
19290
19952
|
// Trim queue if exceeded MAX_EVENTS
|
|
19291
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19953
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19292
19954
|
this._eventList.pop();
|
|
19293
19955
|
}
|
|
19294
19956
|
}
|
|
@@ -19331,7 +19993,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19331
19993
|
// If no previous waiting event found, add new event
|
|
19332
19994
|
this._eventList.unshift(newEvent);
|
|
19333
19995
|
// Trim queue if exceeded MAX_EVENTS
|
|
19334
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19996
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19335
19997
|
this._eventList.pop();
|
|
19336
19998
|
}
|
|
19337
19999
|
}
|
|
@@ -19360,7 +20022,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19360
20022
|
scheduledAt: data.signal.scheduledAt,
|
|
19361
20023
|
});
|
|
19362
20024
|
// Trim queue if exceeded MAX_EVENTS
|
|
19363
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20025
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19364
20026
|
this._eventList.pop();
|
|
19365
20027
|
}
|
|
19366
20028
|
}
|
|
@@ -19499,7 +20161,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19499
20161
|
async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
19500
20162
|
const markdown = await this.getReport(strategyName, columns);
|
|
19501
20163
|
const timestamp = getContextTimestamp();
|
|
19502
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20164
|
+
const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
19503
20165
|
await Markdown.writeData("live", markdown, {
|
|
19504
20166
|
path,
|
|
19505
20167
|
signalId: "",
|
|
@@ -19549,7 +20211,7 @@ class LiveMarkdownService {
|
|
|
19549
20211
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19550
20212
|
* Each combination gets its own isolated storage instance.
|
|
19551
20213
|
*/
|
|
19552
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20214
|
+
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
20215
|
/**
|
|
19554
20216
|
* Subscribes to live signal emitter to receive tick events.
|
|
19555
20217
|
* Protected against multiple subscriptions.
|
|
@@ -19767,7 +20429,7 @@ class LiveMarkdownService {
|
|
|
19767
20429
|
payload,
|
|
19768
20430
|
});
|
|
19769
20431
|
if (payload) {
|
|
19770
|
-
const key = CREATE_KEY_FN$
|
|
20432
|
+
const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19771
20433
|
this.getStorage.clear(key);
|
|
19772
20434
|
}
|
|
19773
20435
|
else {
|
|
@@ -19787,7 +20449,7 @@ class LiveMarkdownService {
|
|
|
19787
20449
|
* @param backtest - Whether running in backtest mode
|
|
19788
20450
|
* @returns Unique string key for memoization
|
|
19789
20451
|
*/
|
|
19790
|
-
const CREATE_KEY_FN$
|
|
20452
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19791
20453
|
const parts = [symbol, strategyName, exchangeName];
|
|
19792
20454
|
if (frameName)
|
|
19793
20455
|
parts.push(frameName);
|
|
@@ -19804,7 +20466,7 @@ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19804
20466
|
* @param timestamp - Unix timestamp in milliseconds
|
|
19805
20467
|
* @returns Filename string
|
|
19806
20468
|
*/
|
|
19807
|
-
const CREATE_FILE_NAME_FN$
|
|
20469
|
+
const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19808
20470
|
const parts = [symbol, strategyName, exchangeName];
|
|
19809
20471
|
if (frameName) {
|
|
19810
20472
|
parts.push(frameName);
|
|
@@ -19815,12 +20477,12 @@ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
19815
20477
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
19816
20478
|
};
|
|
19817
20479
|
/** Maximum number of events to store in schedule reports */
|
|
19818
|
-
const MAX_EVENTS$
|
|
20480
|
+
const MAX_EVENTS$8 = 250;
|
|
19819
20481
|
/**
|
|
19820
20482
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
19821
20483
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
19822
20484
|
*/
|
|
19823
|
-
let ReportStorage$
|
|
20485
|
+
let ReportStorage$7 = class ReportStorage {
|
|
19824
20486
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19825
20487
|
this.symbol = symbol;
|
|
19826
20488
|
this.strategyName = strategyName;
|
|
@@ -19856,7 +20518,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19856
20518
|
scheduledAt: data.signal.scheduledAt,
|
|
19857
20519
|
});
|
|
19858
20520
|
// Trim queue if exceeded MAX_EVENTS
|
|
19859
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20521
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19860
20522
|
this._eventList.pop();
|
|
19861
20523
|
}
|
|
19862
20524
|
}
|
|
@@ -19892,7 +20554,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19892
20554
|
};
|
|
19893
20555
|
this._eventList.unshift(newEvent);
|
|
19894
20556
|
// Trim queue if exceeded MAX_EVENTS
|
|
19895
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20557
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19896
20558
|
this._eventList.pop();
|
|
19897
20559
|
}
|
|
19898
20560
|
}
|
|
@@ -19930,7 +20592,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19930
20592
|
};
|
|
19931
20593
|
this._eventList.unshift(newEvent);
|
|
19932
20594
|
// Trim queue if exceeded MAX_EVENTS
|
|
19933
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20595
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19934
20596
|
this._eventList.pop();
|
|
19935
20597
|
}
|
|
19936
20598
|
}
|
|
@@ -20037,7 +20699,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
20037
20699
|
async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
20038
20700
|
const markdown = await this.getReport(strategyName, columns);
|
|
20039
20701
|
const timestamp = getContextTimestamp();
|
|
20040
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20702
|
+
const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20041
20703
|
await Markdown.writeData("schedule", markdown, {
|
|
20042
20704
|
path,
|
|
20043
20705
|
file: filename,
|
|
@@ -20078,7 +20740,7 @@ class ScheduleMarkdownService {
|
|
|
20078
20740
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20079
20741
|
* Each combination gets its own isolated storage instance.
|
|
20080
20742
|
*/
|
|
20081
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20743
|
+
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
20744
|
/**
|
|
20083
20745
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
20084
20746
|
* Protected against multiple subscriptions.
|
|
@@ -20281,7 +20943,7 @@ class ScheduleMarkdownService {
|
|
|
20281
20943
|
payload,
|
|
20282
20944
|
});
|
|
20283
20945
|
if (payload) {
|
|
20284
|
-
const key = CREATE_KEY_FN$
|
|
20946
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20285
20947
|
this.getStorage.clear(key);
|
|
20286
20948
|
}
|
|
20287
20949
|
else {
|
|
@@ -20301,7 +20963,7 @@ class ScheduleMarkdownService {
|
|
|
20301
20963
|
* @param backtest - Whether running in backtest mode
|
|
20302
20964
|
* @returns Unique string key for memoization
|
|
20303
20965
|
*/
|
|
20304
|
-
const CREATE_KEY_FN$
|
|
20966
|
+
const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20305
20967
|
const parts = [symbol, strategyName, exchangeName];
|
|
20306
20968
|
if (frameName)
|
|
20307
20969
|
parts.push(frameName);
|
|
@@ -20318,7 +20980,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20318
20980
|
* @param timestamp - Unix timestamp in milliseconds
|
|
20319
20981
|
* @returns Filename string
|
|
20320
20982
|
*/
|
|
20321
|
-
const CREATE_FILE_NAME_FN$
|
|
20983
|
+
const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20322
20984
|
const parts = [symbol, strategyName, exchangeName];
|
|
20323
20985
|
if (frameName) {
|
|
20324
20986
|
parts.push(frameName);
|
|
@@ -20338,7 +21000,7 @@ function percentile(sortedArray, p) {
|
|
|
20338
21000
|
return sortedArray[Math.max(0, index)];
|
|
20339
21001
|
}
|
|
20340
21002
|
/** Maximum number of performance events to store per strategy */
|
|
20341
|
-
const MAX_EVENTS$
|
|
21003
|
+
const MAX_EVENTS$7 = 10000;
|
|
20342
21004
|
/**
|
|
20343
21005
|
* Storage class for accumulating performance metrics per strategy.
|
|
20344
21006
|
* Maintains a list of all performance events and provides aggregated statistics.
|
|
@@ -20360,7 +21022,7 @@ class PerformanceStorage {
|
|
|
20360
21022
|
addEvent(event) {
|
|
20361
21023
|
this._events.unshift(event);
|
|
20362
21024
|
// Trim queue if exceeded MAX_EVENTS (keep most recent)
|
|
20363
|
-
if (this._events.length > MAX_EVENTS$
|
|
21025
|
+
if (this._events.length > MAX_EVENTS$7) {
|
|
20364
21026
|
this._events.pop();
|
|
20365
21027
|
}
|
|
20366
21028
|
}
|
|
@@ -20477,6 +21139,10 @@ class PerformanceStorage {
|
|
|
20477
21139
|
return [
|
|
20478
21140
|
`# Performance Report: ${strategyName}`,
|
|
20479
21141
|
"",
|
|
21142
|
+
summaryTable,
|
|
21143
|
+
"",
|
|
21144
|
+
"**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type.",
|
|
21145
|
+
"",
|
|
20480
21146
|
`**Total events:** ${stats.totalEvents}`,
|
|
20481
21147
|
`**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`,
|
|
20482
21148
|
`**Number of metric types:** ${Object.keys(stats.metricStats).length}`,
|
|
@@ -20485,11 +21151,6 @@ class PerformanceStorage {
|
|
|
20485
21151
|
"",
|
|
20486
21152
|
percentages.join("\n"),
|
|
20487
21153
|
"",
|
|
20488
|
-
"## Detailed Metrics",
|
|
20489
|
-
"",
|
|
20490
|
-
summaryTable,
|
|
20491
|
-
"",
|
|
20492
|
-
"**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type."
|
|
20493
21154
|
].join("\n");
|
|
20494
21155
|
}
|
|
20495
21156
|
/**
|
|
@@ -20502,7 +21163,7 @@ class PerformanceStorage {
|
|
|
20502
21163
|
async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
20503
21164
|
const markdown = await this.getReport(strategyName, columns);
|
|
20504
21165
|
const timestamp = getContextTimestamp();
|
|
20505
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21166
|
+
const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20506
21167
|
await Markdown.writeData("performance", markdown, {
|
|
20507
21168
|
path,
|
|
20508
21169
|
file: filename,
|
|
@@ -20549,7 +21210,7 @@ class PerformanceMarkdownService {
|
|
|
20549
21210
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20550
21211
|
* Each combination gets its own isolated storage instance.
|
|
20551
21212
|
*/
|
|
20552
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21213
|
+
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
21214
|
/**
|
|
20554
21215
|
* Subscribes to performance emitter to receive performance events.
|
|
20555
21216
|
* Protected against multiple subscriptions.
|
|
@@ -20716,7 +21377,7 @@ class PerformanceMarkdownService {
|
|
|
20716
21377
|
payload,
|
|
20717
21378
|
});
|
|
20718
21379
|
if (payload) {
|
|
20719
|
-
const key = CREATE_KEY_FN$
|
|
21380
|
+
const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20720
21381
|
this.getStorage.clear(key);
|
|
20721
21382
|
}
|
|
20722
21383
|
else {
|
|
@@ -20730,7 +21391,7 @@ class PerformanceMarkdownService {
|
|
|
20730
21391
|
* Creates a filename for markdown report based on walker name.
|
|
20731
21392
|
* Filename format: "walkerName-timestamp.md"
|
|
20732
21393
|
*/
|
|
20733
|
-
const CREATE_FILE_NAME_FN$
|
|
21394
|
+
const CREATE_FILE_NAME_FN$7 = (walkerName, timestamp) => {
|
|
20734
21395
|
return `${walkerName}-${timestamp}.md`;
|
|
20735
21396
|
};
|
|
20736
21397
|
/**
|
|
@@ -20765,7 +21426,7 @@ function formatMetric(value) {
|
|
|
20765
21426
|
* Storage class for accumulating walker results.
|
|
20766
21427
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
20767
21428
|
*/
|
|
20768
|
-
let ReportStorage$
|
|
21429
|
+
let ReportStorage$6 = class ReportStorage {
|
|
20769
21430
|
constructor(walkerName) {
|
|
20770
21431
|
this.walkerName = walkerName;
|
|
20771
21432
|
/** Walker metadata (set from first addResult call) */
|
|
@@ -20953,7 +21614,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
20953
21614
|
async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
20954
21615
|
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
20955
21616
|
const timestamp = getContextTimestamp();
|
|
20956
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21617
|
+
const filename = CREATE_FILE_NAME_FN$7(this.walkerName, timestamp);
|
|
20957
21618
|
await Markdown.writeData("walker", markdown, {
|
|
20958
21619
|
path,
|
|
20959
21620
|
file: filename,
|
|
@@ -20989,7 +21650,7 @@ class WalkerMarkdownService {
|
|
|
20989
21650
|
* Memoized function to get or create ReportStorage for a walker.
|
|
20990
21651
|
* Each walker gets its own isolated storage instance.
|
|
20991
21652
|
*/
|
|
20992
|
-
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$
|
|
21653
|
+
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$6(walkerName));
|
|
20993
21654
|
/**
|
|
20994
21655
|
* Subscribes to walker emitter to receive walker progress events.
|
|
20995
21656
|
* Protected against multiple subscriptions.
|
|
@@ -21186,7 +21847,7 @@ class WalkerMarkdownService {
|
|
|
21186
21847
|
* @param backtest - Whether running in backtest mode
|
|
21187
21848
|
* @returns Unique string key for memoization
|
|
21188
21849
|
*/
|
|
21189
|
-
const CREATE_KEY_FN$
|
|
21850
|
+
const CREATE_KEY_FN$e = (exchangeName, frameName, backtest) => {
|
|
21190
21851
|
const parts = [exchangeName];
|
|
21191
21852
|
if (frameName)
|
|
21192
21853
|
parts.push(frameName);
|
|
@@ -21197,7 +21858,7 @@ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
|
|
|
21197
21858
|
* Creates a filename for markdown report based on memoization key components.
|
|
21198
21859
|
* Filename format: "strategyName_exchangeName_frameName-timestamp.md"
|
|
21199
21860
|
*/
|
|
21200
|
-
const CREATE_FILE_NAME_FN$
|
|
21861
|
+
const CREATE_FILE_NAME_FN$6 = (strategyName, exchangeName, frameName, timestamp) => {
|
|
21201
21862
|
const parts = [strategyName, exchangeName];
|
|
21202
21863
|
if (frameName) {
|
|
21203
21864
|
parts.push(frameName);
|
|
@@ -21230,7 +21891,7 @@ function isUnsafe(value) {
|
|
|
21230
21891
|
return false;
|
|
21231
21892
|
}
|
|
21232
21893
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
21233
|
-
const MAX_EVENTS$
|
|
21894
|
+
const MAX_EVENTS$6 = 250;
|
|
21234
21895
|
/**
|
|
21235
21896
|
* Storage class for accumulating closed signals per strategy and generating heatmap.
|
|
21236
21897
|
* Maintains symbol-level statistics and provides portfolio-wide metrics.
|
|
@@ -21256,7 +21917,7 @@ class HeatmapStorage {
|
|
|
21256
21917
|
const signals = this.symbolData.get(symbol);
|
|
21257
21918
|
signals.unshift(data);
|
|
21258
21919
|
// Trim queue if exceeded MAX_EVENTS per symbol
|
|
21259
|
-
if (signals.length > MAX_EVENTS$
|
|
21920
|
+
if (signals.length > MAX_EVENTS$6) {
|
|
21260
21921
|
signals.pop();
|
|
21261
21922
|
}
|
|
21262
21923
|
}
|
|
@@ -21507,7 +22168,7 @@ class HeatmapStorage {
|
|
|
21507
22168
|
async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
21508
22169
|
const markdown = await this.getReport(strategyName, columns);
|
|
21509
22170
|
const timestamp = getContextTimestamp();
|
|
21510
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
22171
|
+
const filename = CREATE_FILE_NAME_FN$6(strategyName, this.exchangeName, this.frameName, timestamp);
|
|
21511
22172
|
await Markdown.writeData("heat", markdown, {
|
|
21512
22173
|
path,
|
|
21513
22174
|
file: filename,
|
|
@@ -21553,7 +22214,7 @@ class HeatMarkdownService {
|
|
|
21553
22214
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
21554
22215
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
21555
22216
|
*/
|
|
21556
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22217
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
21557
22218
|
/**
|
|
21558
22219
|
* Subscribes to signal emitter to receive tick events.
|
|
21559
22220
|
* Protected against multiple subscriptions.
|
|
@@ -21748,7 +22409,7 @@ class HeatMarkdownService {
|
|
|
21748
22409
|
payload,
|
|
21749
22410
|
});
|
|
21750
22411
|
if (payload) {
|
|
21751
|
-
const key = CREATE_KEY_FN$
|
|
22412
|
+
const key = CREATE_KEY_FN$e(payload.exchangeName, payload.frameName, payload.backtest);
|
|
21752
22413
|
this.getStorage.clear(key);
|
|
21753
22414
|
}
|
|
21754
22415
|
else {
|
|
@@ -22779,7 +23440,7 @@ class ClientPartial {
|
|
|
22779
23440
|
* @param backtest - Whether running in backtest mode
|
|
22780
23441
|
* @returns Unique string key for memoization
|
|
22781
23442
|
*/
|
|
22782
|
-
const CREATE_KEY_FN$
|
|
23443
|
+
const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
22783
23444
|
/**
|
|
22784
23445
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
22785
23446
|
*
|
|
@@ -22901,7 +23562,7 @@ class PartialConnectionService {
|
|
|
22901
23562
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
22902
23563
|
* Value: ClientPartial instance with logger and event emitters
|
|
22903
23564
|
*/
|
|
22904
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
23565
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
|
|
22905
23566
|
return new ClientPartial({
|
|
22906
23567
|
signalId,
|
|
22907
23568
|
logger: this.loggerService,
|
|
@@ -22991,7 +23652,7 @@ class PartialConnectionService {
|
|
|
22991
23652
|
const partial = this.getPartial(data.id, backtest);
|
|
22992
23653
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
22993
23654
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
22994
|
-
const key = CREATE_KEY_FN$
|
|
23655
|
+
const key = CREATE_KEY_FN$d(data.id, backtest);
|
|
22995
23656
|
this.getPartial.clear(key);
|
|
22996
23657
|
};
|
|
22997
23658
|
}
|
|
@@ -23007,7 +23668,7 @@ class PartialConnectionService {
|
|
|
23007
23668
|
* @param backtest - Whether running in backtest mode
|
|
23008
23669
|
* @returns Unique string key for memoization
|
|
23009
23670
|
*/
|
|
23010
|
-
const CREATE_KEY_FN$
|
|
23671
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
23011
23672
|
const parts = [symbol, strategyName, exchangeName];
|
|
23012
23673
|
if (frameName)
|
|
23013
23674
|
parts.push(frameName);
|
|
@@ -23018,7 +23679,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
23018
23679
|
* Creates a filename for markdown report based on memoization key components.
|
|
23019
23680
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
23020
23681
|
*/
|
|
23021
|
-
const CREATE_FILE_NAME_FN$
|
|
23682
|
+
const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
23022
23683
|
const parts = [symbol, strategyName, exchangeName];
|
|
23023
23684
|
if (frameName) {
|
|
23024
23685
|
parts.push(frameName);
|
|
@@ -23029,12 +23690,12 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
23029
23690
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
23030
23691
|
};
|
|
23031
23692
|
/** Maximum number of events to store in partial reports */
|
|
23032
|
-
const MAX_EVENTS$
|
|
23693
|
+
const MAX_EVENTS$5 = 250;
|
|
23033
23694
|
/**
|
|
23034
23695
|
* Storage class for accumulating partial profit/loss events per symbol-strategy pair.
|
|
23035
23696
|
* Maintains a chronological list of profit and loss level events.
|
|
23036
23697
|
*/
|
|
23037
|
-
let ReportStorage$
|
|
23698
|
+
let ReportStorage$5 = class ReportStorage {
|
|
23038
23699
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
23039
23700
|
this.symbol = symbol;
|
|
23040
23701
|
this.strategyName = strategyName;
|
|
@@ -23077,7 +23738,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23077
23738
|
backtest,
|
|
23078
23739
|
});
|
|
23079
23740
|
// Trim queue if exceeded MAX_EVENTS
|
|
23080
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
23741
|
+
if (this._eventList.length > MAX_EVENTS$5) {
|
|
23081
23742
|
this._eventList.pop();
|
|
23082
23743
|
}
|
|
23083
23744
|
}
|
|
@@ -23115,7 +23776,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23115
23776
|
backtest,
|
|
23116
23777
|
});
|
|
23117
23778
|
// Trim queue if exceeded MAX_EVENTS
|
|
23118
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
23779
|
+
if (this._eventList.length > MAX_EVENTS$5) {
|
|
23119
23780
|
this._eventList.pop();
|
|
23120
23781
|
}
|
|
23121
23782
|
}
|
|
@@ -23191,7 +23852,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23191
23852
|
async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
23192
23853
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
23193
23854
|
const timestamp = getContextTimestamp();
|
|
23194
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
23855
|
+
const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
23195
23856
|
await Markdown.writeData("partial", markdown, {
|
|
23196
23857
|
path,
|
|
23197
23858
|
file: filename,
|
|
@@ -23232,7 +23893,7 @@ class PartialMarkdownService {
|
|
|
23232
23893
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
23233
23894
|
* Each combination gets its own isolated storage instance.
|
|
23234
23895
|
*/
|
|
23235
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23896
|
+
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
23897
|
/**
|
|
23237
23898
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
23238
23899
|
* Protected against multiple subscriptions.
|
|
@@ -23442,7 +24103,7 @@ class PartialMarkdownService {
|
|
|
23442
24103
|
payload,
|
|
23443
24104
|
});
|
|
23444
24105
|
if (payload) {
|
|
23445
|
-
const key = CREATE_KEY_FN$
|
|
24106
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
23446
24107
|
this.getStorage.clear(key);
|
|
23447
24108
|
}
|
|
23448
24109
|
else {
|
|
@@ -23458,7 +24119,7 @@ class PartialMarkdownService {
|
|
|
23458
24119
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
23459
24120
|
* @returns Unique string key for memoization
|
|
23460
24121
|
*/
|
|
23461
|
-
const CREATE_KEY_FN$
|
|
24122
|
+
const CREATE_KEY_FN$b = (context) => {
|
|
23462
24123
|
const parts = [context.strategyName, context.exchangeName];
|
|
23463
24124
|
if (context.frameName)
|
|
23464
24125
|
parts.push(context.frameName);
|
|
@@ -23532,7 +24193,7 @@ class PartialGlobalService {
|
|
|
23532
24193
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
23533
24194
|
* @param methodName - Name of the calling method for error tracking
|
|
23534
24195
|
*/
|
|
23535
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
24196
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
|
|
23536
24197
|
this.loggerService.log("partialGlobalService validate", {
|
|
23537
24198
|
context,
|
|
23538
24199
|
methodName,
|
|
@@ -23987,7 +24648,7 @@ class ClientBreakeven {
|
|
|
23987
24648
|
* @param backtest - Whether running in backtest mode
|
|
23988
24649
|
* @returns Unique string key for memoization
|
|
23989
24650
|
*/
|
|
23990
|
-
const CREATE_KEY_FN$
|
|
24651
|
+
const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
23991
24652
|
/**
|
|
23992
24653
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
23993
24654
|
*
|
|
@@ -24073,7 +24734,7 @@ class BreakevenConnectionService {
|
|
|
24073
24734
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24074
24735
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
24075
24736
|
*/
|
|
24076
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
24737
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
|
|
24077
24738
|
return new ClientBreakeven({
|
|
24078
24739
|
signalId,
|
|
24079
24740
|
logger: this.loggerService,
|
|
@@ -24134,7 +24795,7 @@ class BreakevenConnectionService {
|
|
|
24134
24795
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
24135
24796
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24136
24797
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
24137
|
-
const key = CREATE_KEY_FN$
|
|
24798
|
+
const key = CREATE_KEY_FN$a(data.id, backtest);
|
|
24138
24799
|
this.getBreakeven.clear(key);
|
|
24139
24800
|
};
|
|
24140
24801
|
}
|
|
@@ -24150,7 +24811,7 @@ class BreakevenConnectionService {
|
|
|
24150
24811
|
* @param backtest - Whether running in backtest mode
|
|
24151
24812
|
* @returns Unique string key for memoization
|
|
24152
24813
|
*/
|
|
24153
|
-
const CREATE_KEY_FN$
|
|
24814
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24154
24815
|
const parts = [symbol, strategyName, exchangeName];
|
|
24155
24816
|
if (frameName)
|
|
24156
24817
|
parts.push(frameName);
|
|
@@ -24161,7 +24822,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
24161
24822
|
* Creates a filename for markdown report based on memoization key components.
|
|
24162
24823
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
24163
24824
|
*/
|
|
24164
|
-
const CREATE_FILE_NAME_FN$
|
|
24825
|
+
const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
24165
24826
|
const parts = [symbol, strategyName, exchangeName];
|
|
24166
24827
|
if (frameName) {
|
|
24167
24828
|
parts.push(frameName);
|
|
@@ -24172,12 +24833,12 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
24172
24833
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
24173
24834
|
};
|
|
24174
24835
|
/** Maximum number of events to store in breakeven reports */
|
|
24175
|
-
const MAX_EVENTS$
|
|
24836
|
+
const MAX_EVENTS$4 = 250;
|
|
24176
24837
|
/**
|
|
24177
24838
|
* Storage class for accumulating breakeven events per symbol-strategy pair.
|
|
24178
24839
|
* Maintains a chronological list of breakeven events.
|
|
24179
24840
|
*/
|
|
24180
|
-
let ReportStorage$
|
|
24841
|
+
let ReportStorage$4 = class ReportStorage {
|
|
24181
24842
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
24182
24843
|
this.symbol = symbol;
|
|
24183
24844
|
this.strategyName = strategyName;
|
|
@@ -24218,7 +24879,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
24218
24879
|
backtest,
|
|
24219
24880
|
});
|
|
24220
24881
|
// Trim queue if exceeded MAX_EVENTS
|
|
24221
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
24882
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
24222
24883
|
this._eventList.pop();
|
|
24223
24884
|
}
|
|
24224
24885
|
}
|
|
@@ -24286,7 +24947,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
24286
24947
|
async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
|
|
24287
24948
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24288
24949
|
const timestamp = getContextTimestamp();
|
|
24289
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
24950
|
+
const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24290
24951
|
await Markdown.writeData("breakeven", markdown, {
|
|
24291
24952
|
path,
|
|
24292
24953
|
file: filename,
|
|
@@ -24327,7 +24988,7 @@ class BreakevenMarkdownService {
|
|
|
24327
24988
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24328
24989
|
* Each combination gets its own isolated storage instance.
|
|
24329
24990
|
*/
|
|
24330
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24991
|
+
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
24992
|
/**
|
|
24332
24993
|
* Subscribes to breakeven signal emitter to receive events.
|
|
24333
24994
|
* Protected against multiple subscriptions.
|
|
@@ -24516,7 +25177,7 @@ class BreakevenMarkdownService {
|
|
|
24516
25177
|
payload,
|
|
24517
25178
|
});
|
|
24518
25179
|
if (payload) {
|
|
24519
|
-
const key = CREATE_KEY_FN$
|
|
25180
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24520
25181
|
this.getStorage.clear(key);
|
|
24521
25182
|
}
|
|
24522
25183
|
else {
|
|
@@ -24532,7 +25193,7 @@ class BreakevenMarkdownService {
|
|
|
24532
25193
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
24533
25194
|
* @returns Unique string key for memoization
|
|
24534
25195
|
*/
|
|
24535
|
-
const CREATE_KEY_FN$
|
|
25196
|
+
const CREATE_KEY_FN$8 = (context) => {
|
|
24536
25197
|
const parts = [context.strategyName, context.exchangeName];
|
|
24537
25198
|
if (context.frameName)
|
|
24538
25199
|
parts.push(context.frameName);
|
|
@@ -24606,7 +25267,7 @@ class BreakevenGlobalService {
|
|
|
24606
25267
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
24607
25268
|
* @param methodName - Name of the calling method for error tracking
|
|
24608
25269
|
*/
|
|
24609
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
25270
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
|
|
24610
25271
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
24611
25272
|
context,
|
|
24612
25273
|
methodName,
|
|
@@ -24826,7 +25487,7 @@ class ConfigValidationService {
|
|
|
24826
25487
|
* @param backtest - Whether running in backtest mode
|
|
24827
25488
|
* @returns Unique string key for memoization
|
|
24828
25489
|
*/
|
|
24829
|
-
const CREATE_KEY_FN$
|
|
25490
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24830
25491
|
const parts = [symbol, strategyName, exchangeName];
|
|
24831
25492
|
if (frameName)
|
|
24832
25493
|
parts.push(frameName);
|
|
@@ -24837,7 +25498,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
24837
25498
|
* Creates a filename for markdown report based on memoization key components.
|
|
24838
25499
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
24839
25500
|
*/
|
|
24840
|
-
const CREATE_FILE_NAME_FN$
|
|
25501
|
+
const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
24841
25502
|
const parts = [symbol, strategyName, exchangeName];
|
|
24842
25503
|
if (frameName) {
|
|
24843
25504
|
parts.push(frameName);
|
|
@@ -24848,12 +25509,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
24848
25509
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
24849
25510
|
};
|
|
24850
25511
|
/** Maximum number of events to store in risk reports */
|
|
24851
|
-
const MAX_EVENTS$
|
|
25512
|
+
const MAX_EVENTS$3 = 250;
|
|
24852
25513
|
/**
|
|
24853
25514
|
* Storage class for accumulating risk rejection events per symbol-strategy pair.
|
|
24854
25515
|
* Maintains a chronological list of rejected signals due to risk limits.
|
|
24855
25516
|
*/
|
|
24856
|
-
let ReportStorage$
|
|
25517
|
+
let ReportStorage$3 = class ReportStorage {
|
|
24857
25518
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
24858
25519
|
this.symbol = symbol;
|
|
24859
25520
|
this.strategyName = strategyName;
|
|
@@ -24870,7 +25531,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
24870
25531
|
addRejectionEvent(event) {
|
|
24871
25532
|
this._eventList.unshift(event);
|
|
24872
25533
|
// Trim queue if exceeded MAX_EVENTS
|
|
24873
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
25534
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
24874
25535
|
this._eventList.pop();
|
|
24875
25536
|
}
|
|
24876
25537
|
}
|
|
@@ -24954,7 +25615,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
24954
25615
|
async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
24955
25616
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24956
25617
|
const timestamp = getContextTimestamp();
|
|
24957
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
25618
|
+
const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24958
25619
|
await Markdown.writeData("risk", markdown, {
|
|
24959
25620
|
path,
|
|
24960
25621
|
file: filename,
|
|
@@ -24995,7 +25656,7 @@ class RiskMarkdownService {
|
|
|
24995
25656
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24996
25657
|
* Each combination gets its own isolated storage instance.
|
|
24997
25658
|
*/
|
|
24998
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25659
|
+
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
25660
|
/**
|
|
25000
25661
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
25001
25662
|
* Protected against multiple subscriptions.
|
|
@@ -25184,7 +25845,7 @@ class RiskMarkdownService {
|
|
|
25184
25845
|
payload,
|
|
25185
25846
|
});
|
|
25186
25847
|
if (payload) {
|
|
25187
|
-
const key = CREATE_KEY_FN$
|
|
25848
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25188
25849
|
this.getStorage.clear(key);
|
|
25189
25850
|
}
|
|
25190
25851
|
else {
|
|
@@ -25482,6 +26143,7 @@ const WILDCARD_TARGET = {
|
|
|
25482
26143
|
schedule: true,
|
|
25483
26144
|
walker: true,
|
|
25484
26145
|
sync: true,
|
|
26146
|
+
highest_profit: true,
|
|
25485
26147
|
};
|
|
25486
26148
|
/**
|
|
25487
26149
|
* Utility class for managing report services.
|
|
@@ -25519,7 +26181,7 @@ class ReportUtils {
|
|
|
25519
26181
|
*
|
|
25520
26182
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
25521
26183
|
*/
|
|
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) => {
|
|
26184
|
+
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
26185
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
|
|
25524
26186
|
backtest: bt$1,
|
|
25525
26187
|
breakeven,
|
|
@@ -25567,6 +26229,9 @@ class ReportUtils {
|
|
|
25567
26229
|
if (sync) {
|
|
25568
26230
|
unList.push(bt.syncReportService.subscribe());
|
|
25569
26231
|
}
|
|
26232
|
+
if (highest_profit) {
|
|
26233
|
+
unList.push(bt.highestProfitReportService.subscribe());
|
|
26234
|
+
}
|
|
25570
26235
|
return compose(...unList.map((un) => () => void un()));
|
|
25571
26236
|
};
|
|
25572
26237
|
/**
|
|
@@ -25605,7 +26270,7 @@ class ReportUtils {
|
|
|
25605
26270
|
* Report.disable();
|
|
25606
26271
|
* ```
|
|
25607
26272
|
*/
|
|
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) => {
|
|
26273
|
+
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
26274
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
|
|
25610
26275
|
backtest: bt$1,
|
|
25611
26276
|
breakeven,
|
|
@@ -25652,6 +26317,9 @@ class ReportUtils {
|
|
|
25652
26317
|
if (sync) {
|
|
25653
26318
|
bt.syncReportService.unsubscribe();
|
|
25654
26319
|
}
|
|
26320
|
+
if (highest_profit) {
|
|
26321
|
+
bt.highestProfitReportService.unsubscribe();
|
|
26322
|
+
}
|
|
25655
26323
|
};
|
|
25656
26324
|
}
|
|
25657
26325
|
}
|
|
@@ -27859,6 +28527,60 @@ class SyncReportService {
|
|
|
27859
28527
|
}
|
|
27860
28528
|
}
|
|
27861
28529
|
|
|
28530
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE = "HighestProfitReportService.subscribe";
|
|
28531
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE = "HighestProfitReportService.unsubscribe";
|
|
28532
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK = "HighestProfitReportService.tick";
|
|
28533
|
+
/**
|
|
28534
|
+
* Service for logging highest profit events to the JSONL report database.
|
|
28535
|
+
*
|
|
28536
|
+
* Listens to highestProfitSubject and writes each new price record to
|
|
28537
|
+
* Report.writeData() for persistence and analytics.
|
|
28538
|
+
*/
|
|
28539
|
+
class HighestProfitReportService {
|
|
28540
|
+
constructor() {
|
|
28541
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
28542
|
+
this.tick = async (data) => {
|
|
28543
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK, { data });
|
|
28544
|
+
await Report.writeData("highest_profit", {
|
|
28545
|
+
timestamp: data.timestamp,
|
|
28546
|
+
symbol: data.symbol,
|
|
28547
|
+
strategyName: data.signal.strategyName,
|
|
28548
|
+
exchangeName: data.exchangeName,
|
|
28549
|
+
frameName: data.frameName,
|
|
28550
|
+
backtest: data.backtest,
|
|
28551
|
+
signalId: data.signal.id,
|
|
28552
|
+
position: data.signal.position,
|
|
28553
|
+
currentPrice: data.currentPrice,
|
|
28554
|
+
priceOpen: data.signal.priceOpen,
|
|
28555
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
28556
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
28557
|
+
}, {
|
|
28558
|
+
symbol: data.symbol,
|
|
28559
|
+
strategyName: data.signal.strategyName,
|
|
28560
|
+
exchangeName: data.exchangeName,
|
|
28561
|
+
frameName: data.frameName,
|
|
28562
|
+
signalId: data.signal.id,
|
|
28563
|
+
walkerName: "",
|
|
28564
|
+
});
|
|
28565
|
+
};
|
|
28566
|
+
this.subscribe = singleshot(() => {
|
|
28567
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE);
|
|
28568
|
+
const unsub = highestProfitSubject.subscribe(this.tick);
|
|
28569
|
+
return () => {
|
|
28570
|
+
this.subscribe.clear();
|
|
28571
|
+
unsub();
|
|
28572
|
+
};
|
|
28573
|
+
});
|
|
28574
|
+
this.unsubscribe = async () => {
|
|
28575
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE);
|
|
28576
|
+
if (this.subscribe.hasValue()) {
|
|
28577
|
+
const lastSubscription = this.subscribe();
|
|
28578
|
+
lastSubscription();
|
|
28579
|
+
}
|
|
28580
|
+
};
|
|
28581
|
+
}
|
|
28582
|
+
}
|
|
28583
|
+
|
|
27862
28584
|
/**
|
|
27863
28585
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
27864
28586
|
*
|
|
@@ -27872,7 +28594,7 @@ class SyncReportService {
|
|
|
27872
28594
|
* @returns Colon-separated key string for memoization
|
|
27873
28595
|
* @internal
|
|
27874
28596
|
*/
|
|
27875
|
-
const CREATE_KEY_FN$
|
|
28597
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
27876
28598
|
const parts = [symbol, strategyName, exchangeName];
|
|
27877
28599
|
if (frameName)
|
|
27878
28600
|
parts.push(frameName);
|
|
@@ -27892,7 +28614,7 @@ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
27892
28614
|
* @returns Underscore-separated filename with .md extension
|
|
27893
28615
|
* @internal
|
|
27894
28616
|
*/
|
|
27895
|
-
const CREATE_FILE_NAME_FN$
|
|
28617
|
+
const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
27896
28618
|
const parts = [symbol, strategyName, exchangeName];
|
|
27897
28619
|
if (frameName) {
|
|
27898
28620
|
parts.push(frameName);
|
|
@@ -27907,7 +28629,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
27907
28629
|
* Older events are discarded when this limit is exceeded.
|
|
27908
28630
|
* @internal
|
|
27909
28631
|
*/
|
|
27910
|
-
const MAX_EVENTS$
|
|
28632
|
+
const MAX_EVENTS$2 = 250;
|
|
27911
28633
|
/**
|
|
27912
28634
|
* In-memory storage for accumulating strategy events per symbol-strategy pair.
|
|
27913
28635
|
*
|
|
@@ -27920,7 +28642,7 @@ const MAX_EVENTS$1 = 250;
|
|
|
27920
28642
|
*
|
|
27921
28643
|
* @internal
|
|
27922
28644
|
*/
|
|
27923
|
-
let ReportStorage$
|
|
28645
|
+
let ReportStorage$2 = class ReportStorage {
|
|
27924
28646
|
/**
|
|
27925
28647
|
* Creates a new ReportStorage instance.
|
|
27926
28648
|
*
|
|
@@ -27946,7 +28668,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
27946
28668
|
*/
|
|
27947
28669
|
addEvent(event) {
|
|
27948
28670
|
this._eventList.unshift(event);
|
|
27949
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
28671
|
+
if (this._eventList.length > MAX_EVENTS$2) {
|
|
27950
28672
|
this._eventList.pop();
|
|
27951
28673
|
}
|
|
27952
28674
|
}
|
|
@@ -28051,7 +28773,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
28051
28773
|
async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
|
|
28052
28774
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
28053
28775
|
const timestamp = getContextTimestamp();
|
|
28054
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
28776
|
+
const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
28055
28777
|
await Markdown.writeData("strategy", markdown, {
|
|
28056
28778
|
path,
|
|
28057
28779
|
file: filename,
|
|
@@ -28120,7 +28842,7 @@ class StrategyMarkdownService {
|
|
|
28120
28842
|
*
|
|
28121
28843
|
* @internal
|
|
28122
28844
|
*/
|
|
28123
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
28845
|
+
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
28846
|
/**
|
|
28125
28847
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
28126
28848
|
*
|
|
@@ -28688,7 +29410,7 @@ class StrategyMarkdownService {
|
|
|
28688
29410
|
this.clear = async (payload) => {
|
|
28689
29411
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
28690
29412
|
if (payload) {
|
|
28691
|
-
const key = CREATE_KEY_FN$
|
|
29413
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
28692
29414
|
this.getStorage.clear(key);
|
|
28693
29415
|
}
|
|
28694
29416
|
else {
|
|
@@ -28796,7 +29518,7 @@ class StrategyMarkdownService {
|
|
|
28796
29518
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
28797
29519
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
28798
29520
|
*/
|
|
28799
|
-
const CREATE_KEY_FN$
|
|
29521
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
28800
29522
|
const parts = [symbol, strategyName, exchangeName];
|
|
28801
29523
|
if (frameName)
|
|
28802
29524
|
parts.push(frameName);
|
|
@@ -28807,7 +29529,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
28807
29529
|
* Creates a filename for the markdown report.
|
|
28808
29530
|
* Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
|
|
28809
29531
|
*/
|
|
28810
|
-
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
29532
|
+
const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
28811
29533
|
const parts = [symbol, strategyName, exchangeName];
|
|
28812
29534
|
if (frameName) {
|
|
28813
29535
|
parts.push(frameName);
|
|
@@ -28818,12 +29540,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
|
|
|
28818
29540
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
28819
29541
|
};
|
|
28820
29542
|
/** Maximum number of events to store in sync reports */
|
|
28821
|
-
const MAX_EVENTS = 250;
|
|
29543
|
+
const MAX_EVENTS$1 = 250;
|
|
28822
29544
|
/**
|
|
28823
29545
|
* Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
|
|
28824
29546
|
* Maintains a chronological list of signal-open and signal-close events.
|
|
28825
29547
|
*/
|
|
28826
|
-
class ReportStorage {
|
|
29548
|
+
let ReportStorage$1 = class ReportStorage {
|
|
28827
29549
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
28828
29550
|
this.symbol = symbol;
|
|
28829
29551
|
this.strategyName = strategyName;
|
|
@@ -28833,7 +29555,7 @@ class ReportStorage {
|
|
|
28833
29555
|
}
|
|
28834
29556
|
addEvent(event) {
|
|
28835
29557
|
this._eventList.unshift(event);
|
|
28836
|
-
if (this._eventList.length > MAX_EVENTS) {
|
|
29558
|
+
if (this._eventList.length > MAX_EVENTS$1) {
|
|
28837
29559
|
this._eventList.pop();
|
|
28838
29560
|
}
|
|
28839
29561
|
}
|
|
@@ -28894,7 +29616,7 @@ class ReportStorage {
|
|
|
28894
29616
|
async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
|
|
28895
29617
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
28896
29618
|
const timestamp = getContextTimestamp();
|
|
28897
|
-
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
29619
|
+
const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
28898
29620
|
await Markdown.writeData("sync", markdown, {
|
|
28899
29621
|
path,
|
|
28900
29622
|
file: filename,
|
|
@@ -28905,7 +29627,7 @@ class ReportStorage {
|
|
|
28905
29627
|
frameName: this.frameName,
|
|
28906
29628
|
});
|
|
28907
29629
|
}
|
|
28908
|
-
}
|
|
29630
|
+
};
|
|
28909
29631
|
/**
|
|
28910
29632
|
* Service for generating and saving signal sync markdown reports.
|
|
28911
29633
|
*
|
|
@@ -28928,7 +29650,7 @@ class ReportStorage {
|
|
|
28928
29650
|
class SyncMarkdownService {
|
|
28929
29651
|
constructor() {
|
|
28930
29652
|
this.loggerService = inject(TYPES.loggerService);
|
|
28931
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
29653
|
+
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
29654
|
this.subscribe = singleshot(() => {
|
|
28933
29655
|
this.loggerService.log("syncMarkdownService init");
|
|
28934
29656
|
const unsubscribe = syncSubject.subscribe(this.tick);
|
|
@@ -29002,6 +29724,190 @@ class SyncMarkdownService {
|
|
|
29002
29724
|
};
|
|
29003
29725
|
this.clear = async (payload) => {
|
|
29004
29726
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
29727
|
+
if (payload) {
|
|
29728
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29729
|
+
this.getStorage.clear(key);
|
|
29730
|
+
}
|
|
29731
|
+
else {
|
|
29732
|
+
this.getStorage.clear();
|
|
29733
|
+
}
|
|
29734
|
+
};
|
|
29735
|
+
}
|
|
29736
|
+
}
|
|
29737
|
+
|
|
29738
|
+
/**
|
|
29739
|
+
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
29740
|
+
*/
|
|
29741
|
+
const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29742
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29743
|
+
if (frameName)
|
|
29744
|
+
parts.push(frameName);
|
|
29745
|
+
parts.push(backtest ? "backtest" : "live");
|
|
29746
|
+
return parts.join(":");
|
|
29747
|
+
};
|
|
29748
|
+
/**
|
|
29749
|
+
* Creates a filename for the markdown report.
|
|
29750
|
+
*/
|
|
29751
|
+
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
29752
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29753
|
+
if (frameName) {
|
|
29754
|
+
parts.push(frameName);
|
|
29755
|
+
parts.push("backtest");
|
|
29756
|
+
}
|
|
29757
|
+
else
|
|
29758
|
+
parts.push("live");
|
|
29759
|
+
return `${parts.join("_")}-${timestamp}.md`;
|
|
29760
|
+
};
|
|
29761
|
+
/** Maximum number of events to store per combination */
|
|
29762
|
+
const MAX_EVENTS = 250;
|
|
29763
|
+
/**
|
|
29764
|
+
* Accumulates highest profit events per symbol-strategy-exchange-frame combination.
|
|
29765
|
+
*/
|
|
29766
|
+
class ReportStorage {
|
|
29767
|
+
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
29768
|
+
this.symbol = symbol;
|
|
29769
|
+
this.strategyName = strategyName;
|
|
29770
|
+
this.exchangeName = exchangeName;
|
|
29771
|
+
this.frameName = frameName;
|
|
29772
|
+
this._eventList = [];
|
|
29773
|
+
}
|
|
29774
|
+
/**
|
|
29775
|
+
* Adds a highest profit event to the storage.
|
|
29776
|
+
*/
|
|
29777
|
+
addEvent(data, currentPrice, backtest, timestamp) {
|
|
29778
|
+
this._eventList.unshift({
|
|
29779
|
+
timestamp,
|
|
29780
|
+
symbol: data.symbol,
|
|
29781
|
+
strategyName: data.strategyName,
|
|
29782
|
+
signalId: data.id,
|
|
29783
|
+
position: data.position,
|
|
29784
|
+
pnl: data.pnl,
|
|
29785
|
+
currentPrice,
|
|
29786
|
+
priceOpen: data.priceOpen,
|
|
29787
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
29788
|
+
priceStopLoss: data.priceStopLoss,
|
|
29789
|
+
backtest,
|
|
29790
|
+
});
|
|
29791
|
+
if (this._eventList.length > MAX_EVENTS) {
|
|
29792
|
+
this._eventList.pop();
|
|
29793
|
+
}
|
|
29794
|
+
}
|
|
29795
|
+
/**
|
|
29796
|
+
* Returns aggregated statistics from accumulated events.
|
|
29797
|
+
*/
|
|
29798
|
+
async getData() {
|
|
29799
|
+
return {
|
|
29800
|
+
eventList: this._eventList,
|
|
29801
|
+
totalEvents: this._eventList.length,
|
|
29802
|
+
};
|
|
29803
|
+
}
|
|
29804
|
+
/**
|
|
29805
|
+
* Generates a markdown report table for this storage.
|
|
29806
|
+
*/
|
|
29807
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.highest_profit_columns) {
|
|
29808
|
+
const stats = await this.getData();
|
|
29809
|
+
if (stats.totalEvents === 0) {
|
|
29810
|
+
return [
|
|
29811
|
+
`# Highest Profit Report: ${symbol}:${strategyName}`,
|
|
29812
|
+
"",
|
|
29813
|
+
"No highest profit events recorded yet.",
|
|
29814
|
+
].join("\n");
|
|
29815
|
+
}
|
|
29816
|
+
const visibleColumns = [];
|
|
29817
|
+
for (const col of columns) {
|
|
29818
|
+
if (await col.isVisible()) {
|
|
29819
|
+
visibleColumns.push(col);
|
|
29820
|
+
}
|
|
29821
|
+
}
|
|
29822
|
+
const header = visibleColumns.map((col) => col.label);
|
|
29823
|
+
const separator = visibleColumns.map(() => "---");
|
|
29824
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
29825
|
+
const tableData = [header, separator, ...rows];
|
|
29826
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
29827
|
+
return [
|
|
29828
|
+
`# Highest Profit Report: ${symbol}:${strategyName}`,
|
|
29829
|
+
"",
|
|
29830
|
+
table,
|
|
29831
|
+
"",
|
|
29832
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
29833
|
+
].join("\n");
|
|
29834
|
+
}
|
|
29835
|
+
/**
|
|
29836
|
+
* Saves the report to disk.
|
|
29837
|
+
*/
|
|
29838
|
+
async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
|
|
29839
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
29840
|
+
const timestamp = getContextTimestamp();
|
|
29841
|
+
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
29842
|
+
await Markdown.writeData("highest_profit", markdown, {
|
|
29843
|
+
path,
|
|
29844
|
+
file: filename,
|
|
29845
|
+
symbol: this.symbol,
|
|
29846
|
+
signalId: "",
|
|
29847
|
+
strategyName: this.strategyName,
|
|
29848
|
+
exchangeName: this.exchangeName,
|
|
29849
|
+
frameName: this.frameName,
|
|
29850
|
+
});
|
|
29851
|
+
}
|
|
29852
|
+
}
|
|
29853
|
+
/**
|
|
29854
|
+
* Service for generating and saving highest profit markdown reports.
|
|
29855
|
+
*
|
|
29856
|
+
* Listens to highestProfitSubject and accumulates events per
|
|
29857
|
+
* symbol-strategy-exchange-frame combination. Provides getData(),
|
|
29858
|
+
* getReport(), and dump() methods matching the Partial pattern.
|
|
29859
|
+
*/
|
|
29860
|
+
class HighestProfitMarkdownService {
|
|
29861
|
+
constructor() {
|
|
29862
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
29863
|
+
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));
|
|
29864
|
+
this.subscribe = singleshot(() => {
|
|
29865
|
+
this.loggerService.log("highestProfitMarkdownService init");
|
|
29866
|
+
const unsub = highestProfitSubject.subscribe(this.tick);
|
|
29867
|
+
return () => {
|
|
29868
|
+
this.subscribe.clear();
|
|
29869
|
+
this.clear();
|
|
29870
|
+
unsub();
|
|
29871
|
+
};
|
|
29872
|
+
});
|
|
29873
|
+
this.unsubscribe = async () => {
|
|
29874
|
+
this.loggerService.log("highestProfitMarkdownService unsubscribe");
|
|
29875
|
+
if (this.subscribe.hasValue()) {
|
|
29876
|
+
const lastSubscription = this.subscribe();
|
|
29877
|
+
lastSubscription();
|
|
29878
|
+
}
|
|
29879
|
+
};
|
|
29880
|
+
this.tick = async (data) => {
|
|
29881
|
+
this.loggerService.log("highestProfitMarkdownService tick", { data });
|
|
29882
|
+
const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
|
|
29883
|
+
storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
|
|
29884
|
+
};
|
|
29885
|
+
this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29886
|
+
this.loggerService.log("highestProfitMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
29887
|
+
if (!this.subscribe.hasValue()) {
|
|
29888
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before getting data.");
|
|
29889
|
+
}
|
|
29890
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29891
|
+
return storage.getData();
|
|
29892
|
+
};
|
|
29893
|
+
this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.highest_profit_columns) => {
|
|
29894
|
+
this.loggerService.log("highestProfitMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
29895
|
+
if (!this.subscribe.hasValue()) {
|
|
29896
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before generating reports.");
|
|
29897
|
+
}
|
|
29898
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29899
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
29900
|
+
};
|
|
29901
|
+
this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) => {
|
|
29902
|
+
this.loggerService.log("highestProfitMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
|
|
29903
|
+
if (!this.subscribe.hasValue()) {
|
|
29904
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before dumping reports.");
|
|
29905
|
+
}
|
|
29906
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29907
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
29908
|
+
};
|
|
29909
|
+
this.clear = async (payload) => {
|
|
29910
|
+
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
29005
29911
|
if (payload) {
|
|
29006
29912
|
const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29007
29913
|
this.getStorage.clear(key);
|
|
@@ -29351,6 +30257,7 @@ class TimeMetaService {
|
|
|
29351
30257
|
provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
|
|
29352
30258
|
provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
|
|
29353
30259
|
provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
|
|
30260
|
+
provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
|
|
29354
30261
|
}
|
|
29355
30262
|
{
|
|
29356
30263
|
provide(TYPES.backtestReportService, () => new BacktestReportService());
|
|
@@ -29364,6 +30271,7 @@ class TimeMetaService {
|
|
|
29364
30271
|
provide(TYPES.riskReportService, () => new RiskReportService());
|
|
29365
30272
|
provide(TYPES.strategyReportService, () => new StrategyReportService());
|
|
29366
30273
|
provide(TYPES.syncReportService, () => new SyncReportService());
|
|
30274
|
+
provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
|
|
29367
30275
|
}
|
|
29368
30276
|
{
|
|
29369
30277
|
provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
|
|
@@ -29446,6 +30354,7 @@ const markdownServices = {
|
|
|
29446
30354
|
riskMarkdownService: inject(TYPES.riskMarkdownService),
|
|
29447
30355
|
strategyMarkdownService: inject(TYPES.strategyMarkdownService),
|
|
29448
30356
|
syncMarkdownService: inject(TYPES.syncMarkdownService),
|
|
30357
|
+
highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
|
|
29449
30358
|
};
|
|
29450
30359
|
const reportServices = {
|
|
29451
30360
|
backtestReportService: inject(TYPES.backtestReportService),
|
|
@@ -29459,6 +30368,7 @@ const reportServices = {
|
|
|
29459
30368
|
riskReportService: inject(TYPES.riskReportService),
|
|
29460
30369
|
strategyReportService: inject(TYPES.strategyReportService),
|
|
29461
30370
|
syncReportService: inject(TYPES.syncReportService),
|
|
30371
|
+
highestProfitReportService: inject(TYPES.highestProfitReportService),
|
|
29462
30372
|
};
|
|
29463
30373
|
const validationServices = {
|
|
29464
30374
|
exchangeValidationService: inject(TYPES.exchangeValidationService),
|
|
@@ -31230,7 +32140,7 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
|
|
|
31230
32140
|
*
|
|
31231
32141
|
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
31232
32142
|
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
31233
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32143
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31234
32144
|
* @returns percentShift to pass to `commitTrailingStop`
|
|
31235
32145
|
*
|
|
31236
32146
|
* @example
|
|
@@ -31252,7 +32162,7 @@ const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectiv
|
|
|
31252
32162
|
*
|
|
31253
32163
|
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
31254
32164
|
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
31255
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32165
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31256
32166
|
* @returns percentShift to pass to `commitTrailingTake`
|
|
31257
32167
|
*
|
|
31258
32168
|
* @example
|
|
@@ -31277,7 +32187,7 @@ const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effe
|
|
|
31277
32187
|
*
|
|
31278
32188
|
* @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
|
|
31279
32189
|
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
31280
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32190
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31281
32191
|
* @param position - Position direction: "long" or "short"
|
|
31282
32192
|
* @returns Absolute stop-loss price corresponding to the given percentShift
|
|
31283
32193
|
*
|
|
@@ -31304,7 +32214,7 @@ const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePri
|
|
|
31304
32214
|
*
|
|
31305
32215
|
* @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
|
|
31306
32216
|
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
31307
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32217
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31308
32218
|
* @param position - Position direction: "long" or "short"
|
|
31309
32219
|
* @returns Absolute take-profit price corresponding to the given percentShift
|
|
31310
32220
|
*
|
|
@@ -31343,7 +32253,7 @@ const percentToCloseCost = (percentToClose, investedCost) => {
|
|
|
31343
32253
|
* Breakeven moves the SL to the effective entry price (effectivePriceOpen).
|
|
31344
32254
|
* The value is the same regardless of position direction.
|
|
31345
32255
|
*
|
|
31346
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32256
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31347
32257
|
* @returns New stop-loss price equal to the effective entry price
|
|
31348
32258
|
*
|
|
31349
32259
|
* @example
|
|
@@ -32265,13 +33175,22 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
|
|
|
32265
33175
|
const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
|
|
32266
33176
|
const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
|
|
32267
33177
|
const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
|
|
32268
|
-
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.
|
|
33178
|
+
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionEffectivePrice";
|
|
32269
33179
|
const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
|
|
32270
33180
|
const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
|
|
32271
33181
|
const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
32272
33182
|
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
32273
33183
|
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
32274
33184
|
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
33185
|
+
const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
|
|
33186
|
+
const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
|
|
33187
|
+
const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
|
|
33188
|
+
const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
|
|
33189
|
+
const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
|
|
33190
|
+
const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
|
|
33191
|
+
const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
|
|
33192
|
+
const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
|
|
33193
|
+
const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
|
|
32275
33194
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
32276
33195
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
32277
33196
|
/**
|
|
@@ -32538,7 +33457,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
32538
33457
|
if (!signal) {
|
|
32539
33458
|
return false;
|
|
32540
33459
|
}
|
|
32541
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33460
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32542
33461
|
if (effectivePriceOpen === null) {
|
|
32543
33462
|
return false;
|
|
32544
33463
|
}
|
|
@@ -32618,7 +33537,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
32618
33537
|
if (!signal) {
|
|
32619
33538
|
return false;
|
|
32620
33539
|
}
|
|
32621
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33540
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32622
33541
|
if (effectivePriceOpen === null) {
|
|
32623
33542
|
return false;
|
|
32624
33543
|
}
|
|
@@ -32668,7 +33587,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
|
32668
33587
|
if (!signal) {
|
|
32669
33588
|
return false;
|
|
32670
33589
|
}
|
|
32671
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33590
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32672
33591
|
if (effectivePriceOpen === null) {
|
|
32673
33592
|
return false;
|
|
32674
33593
|
}
|
|
@@ -32719,7 +33638,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
|
32719
33638
|
if (!signal) {
|
|
32720
33639
|
return false;
|
|
32721
33640
|
}
|
|
32722
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33641
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32723
33642
|
if (effectivePriceOpen === null) {
|
|
32724
33643
|
return false;
|
|
32725
33644
|
}
|
|
@@ -32781,7 +33700,7 @@ async function commitBreakeven(symbol) {
|
|
|
32781
33700
|
if (!signal) {
|
|
32782
33701
|
return false;
|
|
32783
33702
|
}
|
|
32784
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33703
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32785
33704
|
if (effectivePriceOpen === null) {
|
|
32786
33705
|
return false;
|
|
32787
33706
|
}
|
|
@@ -33077,26 +33996,26 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
33077
33996
|
*
|
|
33078
33997
|
* @example
|
|
33079
33998
|
* ```typescript
|
|
33080
|
-
* import {
|
|
33999
|
+
* import { getPositionEffectivePrice } from "backtest-kit";
|
|
33081
34000
|
*
|
|
33082
|
-
* const avgPrice = await
|
|
34001
|
+
* const avgPrice = await getPositionEffectivePrice("BTCUSDT");
|
|
33083
34002
|
* // No DCA: avgPrice === priceOpen
|
|
33084
34003
|
* // After DCA at lower price: avgPrice < priceOpen
|
|
33085
34004
|
* ```
|
|
33086
34005
|
*/
|
|
33087
|
-
async function
|
|
34006
|
+
async function getPositionEffectivePrice(symbol) {
|
|
33088
34007
|
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
33089
34008
|
symbol,
|
|
33090
34009
|
});
|
|
33091
34010
|
if (!ExecutionContextService.hasContext()) {
|
|
33092
|
-
throw new Error("
|
|
34011
|
+
throw new Error("getPositionEffectivePrice requires an execution context");
|
|
33093
34012
|
}
|
|
33094
34013
|
if (!MethodContextService.hasContext()) {
|
|
33095
|
-
throw new Error("
|
|
34014
|
+
throw new Error("getPositionEffectivePrice requires a method context");
|
|
33096
34015
|
}
|
|
33097
34016
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33098
34017
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33099
|
-
return await bt.strategyCoreService.
|
|
34018
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33100
34019
|
}
|
|
33101
34020
|
/**
|
|
33102
34021
|
* Returns the number of DCA entries made for the current pending signal.
|
|
@@ -33451,6 +34370,284 @@ async function getPositionPartials(symbol) {
|
|
|
33451
34370
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33452
34371
|
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33453
34372
|
}
|
|
34373
|
+
/**
|
|
34374
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
34375
|
+
*
|
|
34376
|
+
* Each element represents a single position entry — the initial open or a subsequent
|
|
34377
|
+
* DCA entry added via commitAverageBuy.
|
|
34378
|
+
*
|
|
34379
|
+
* Returns null if no pending signal exists.
|
|
34380
|
+
* Returns a single-element array if no DCA entries were made.
|
|
34381
|
+
*
|
|
34382
|
+
* Each entry contains:
|
|
34383
|
+
* - `price` — execution price of this entry
|
|
34384
|
+
* - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
|
|
34385
|
+
*
|
|
34386
|
+
* @param symbol - Trading pair symbol
|
|
34387
|
+
* @returns Promise resolving to array of entry records or null
|
|
34388
|
+
*
|
|
34389
|
+
* @example
|
|
34390
|
+
* ```typescript
|
|
34391
|
+
* import { getPositionEntries } from "backtest-kit";
|
|
34392
|
+
*
|
|
34393
|
+
* const entries = await getPositionEntries("BTCUSDT");
|
|
34394
|
+
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
34395
|
+
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
34396
|
+
* ```
|
|
34397
|
+
*/
|
|
34398
|
+
async function getPositionEntries(symbol) {
|
|
34399
|
+
bt.loggerService.info(GET_POSITION_ENTRIES_METHOD_NAME, { symbol });
|
|
34400
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34401
|
+
throw new Error("getPositionEntries requires an execution context");
|
|
34402
|
+
}
|
|
34403
|
+
if (!MethodContextService.hasContext()) {
|
|
34404
|
+
throw new Error("getPositionEntries requires a method context");
|
|
34405
|
+
}
|
|
34406
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34407
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34408
|
+
return await bt.strategyCoreService.getPositionEntries(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34409
|
+
}
|
|
34410
|
+
/**
|
|
34411
|
+
* Returns the original estimated duration for the current pending signal.
|
|
34412
|
+
*
|
|
34413
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
34414
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
34415
|
+
*
|
|
34416
|
+
* Returns null if no pending signal exists.
|
|
34417
|
+
*
|
|
34418
|
+
* @param symbol - Trading pair symbol
|
|
34419
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
34420
|
+
*
|
|
34421
|
+
* @example
|
|
34422
|
+
* ```typescript
|
|
34423
|
+
* import { getPositionEstimateMinutes } from "backtest-kit";
|
|
34424
|
+
*
|
|
34425
|
+
* const estimate = await getPositionEstimateMinutes("BTCUSDT");
|
|
34426
|
+
* // e.g. 120 (2 hours)
|
|
34427
|
+
* ```
|
|
34428
|
+
*/
|
|
34429
|
+
async function getPositionEstimateMinutes(symbol) {
|
|
34430
|
+
bt.loggerService.info(GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME, { symbol });
|
|
34431
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34432
|
+
throw new Error("getPositionEstimateMinutes requires an execution context");
|
|
34433
|
+
}
|
|
34434
|
+
if (!MethodContextService.hasContext()) {
|
|
34435
|
+
throw new Error("getPositionEstimateMinutes requires a method context");
|
|
34436
|
+
}
|
|
34437
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34438
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34439
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34440
|
+
}
|
|
34441
|
+
/**
|
|
34442
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
34443
|
+
*
|
|
34444
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
34445
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
34446
|
+
*
|
|
34447
|
+
* Returns null if no pending signal exists.
|
|
34448
|
+
*
|
|
34449
|
+
* @param symbol - Trading pair symbol
|
|
34450
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
34451
|
+
*
|
|
34452
|
+
* @example
|
|
34453
|
+
* ```typescript
|
|
34454
|
+
* import { getPositionCountdownMinutes } from "backtest-kit";
|
|
34455
|
+
*
|
|
34456
|
+
* const remaining = await getPositionCountdownMinutes("BTCUSDT");
|
|
34457
|
+
* // e.g. 45 (45 minutes left)
|
|
34458
|
+
* // 0 when expired
|
|
34459
|
+
* ```
|
|
34460
|
+
*/
|
|
34461
|
+
async function getPositionCountdownMinutes(symbol) {
|
|
34462
|
+
bt.loggerService.info(GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME, { symbol });
|
|
34463
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34464
|
+
throw new Error("getPositionCountdownMinutes requires an execution context");
|
|
34465
|
+
}
|
|
34466
|
+
if (!MethodContextService.hasContext()) {
|
|
34467
|
+
throw new Error("getPositionCountdownMinutes requires a method context");
|
|
34468
|
+
}
|
|
34469
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34470
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34471
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34472
|
+
}
|
|
34473
|
+
/**
|
|
34474
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
34475
|
+
*
|
|
34476
|
+
* Initialized at position open with the entry price and timestamp.
|
|
34477
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
34478
|
+
* - LONG: tracks the highest price seen above effective entry
|
|
34479
|
+
* - SHORT: tracks the lowest price seen below effective entry
|
|
34480
|
+
*
|
|
34481
|
+
* Returns null if no pending signal exists.
|
|
34482
|
+
* Never returns null when a signal is active — always contains at least the entry price.
|
|
34483
|
+
*
|
|
34484
|
+
* @param symbol - Trading pair symbol
|
|
34485
|
+
* @returns Promise resolving to `{ price, timestamp }` record or null
|
|
34486
|
+
*
|
|
34487
|
+
* @example
|
|
34488
|
+
* ```typescript
|
|
34489
|
+
* import { getPositionHighestProfitPrice } from "backtest-kit";
|
|
34490
|
+
*
|
|
34491
|
+
* const peak = await getPositionHighestProfitPrice("BTCUSDT");
|
|
34492
|
+
* // e.g. { price: 44500, timestamp: 1700000000000 }
|
|
34493
|
+
* ```
|
|
34494
|
+
*/
|
|
34495
|
+
async function getPositionHighestProfitPrice(symbol) {
|
|
34496
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME, { symbol });
|
|
34497
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34498
|
+
throw new Error("getPositionHighestProfitPrice requires an execution context");
|
|
34499
|
+
}
|
|
34500
|
+
if (!MethodContextService.hasContext()) {
|
|
34501
|
+
throw new Error("getPositionHighestProfitPrice requires a method context");
|
|
34502
|
+
}
|
|
34503
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34504
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34505
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34506
|
+
}
|
|
34507
|
+
/**
|
|
34508
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
34509
|
+
*
|
|
34510
|
+
* Returns null if no pending signal exists.
|
|
34511
|
+
*
|
|
34512
|
+
* @param symbol - Trading pair symbol
|
|
34513
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
34514
|
+
*
|
|
34515
|
+
* @example
|
|
34516
|
+
* ```typescript
|
|
34517
|
+
* import { getPositionHighestProfitTimestamp } from "backtest-kit";
|
|
34518
|
+
*
|
|
34519
|
+
* const ts = await getPositionHighestProfitTimestamp("BTCUSDT");
|
|
34520
|
+
* // e.g. 1700000000000
|
|
34521
|
+
* ```
|
|
34522
|
+
*/
|
|
34523
|
+
async function getPositionHighestProfitTimestamp(symbol) {
|
|
34524
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME, { symbol });
|
|
34525
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34526
|
+
throw new Error("getPositionHighestProfitTimestamp requires an execution context");
|
|
34527
|
+
}
|
|
34528
|
+
if (!MethodContextService.hasContext()) {
|
|
34529
|
+
throw new Error("getPositionHighestProfitTimestamp requires a method context");
|
|
34530
|
+
}
|
|
34531
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34532
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34533
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34534
|
+
}
|
|
34535
|
+
/**
|
|
34536
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
34537
|
+
*
|
|
34538
|
+
* Returns null if no pending signal exists.
|
|
34539
|
+
*
|
|
34540
|
+
* @param symbol - Trading pair symbol
|
|
34541
|
+
* @returns Promise resolving to PnL percentage or null
|
|
34542
|
+
*
|
|
34543
|
+
* @example
|
|
34544
|
+
* ```typescript
|
|
34545
|
+
* import { getPositionHighestPnlPercentage } from "backtest-kit";
|
|
34546
|
+
*
|
|
34547
|
+
* const pnl = await getPositionHighestPnlPercentage("BTCUSDT");
|
|
34548
|
+
* // e.g. 3.5
|
|
34549
|
+
* ```
|
|
34550
|
+
*/
|
|
34551
|
+
async function getPositionHighestPnlPercentage(symbol) {
|
|
34552
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
34553
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34554
|
+
throw new Error("getPositionHighestPnlPercentage requires an execution context");
|
|
34555
|
+
}
|
|
34556
|
+
if (!MethodContextService.hasContext()) {
|
|
34557
|
+
throw new Error("getPositionHighestPnlPercentage requires a method context");
|
|
34558
|
+
}
|
|
34559
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34560
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34561
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34562
|
+
}
|
|
34563
|
+
/**
|
|
34564
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
34565
|
+
*
|
|
34566
|
+
* Returns null if no pending signal exists.
|
|
34567
|
+
*
|
|
34568
|
+
* @param symbol - Trading pair symbol
|
|
34569
|
+
* @returns Promise resolving to PnL cost or null
|
|
34570
|
+
*
|
|
34571
|
+
* @example
|
|
34572
|
+
* ```typescript
|
|
34573
|
+
* import { getPositionHighestPnlCost } from "backtest-kit";
|
|
34574
|
+
*
|
|
34575
|
+
* const pnlCost = await getPositionHighestPnlCost("BTCUSDT");
|
|
34576
|
+
* // e.g. 35.5
|
|
34577
|
+
* ```
|
|
34578
|
+
*/
|
|
34579
|
+
async function getPositionHighestPnlCost(symbol) {
|
|
34580
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME, { symbol });
|
|
34581
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34582
|
+
throw new Error("getPositionHighestPnlCost requires an execution context");
|
|
34583
|
+
}
|
|
34584
|
+
if (!MethodContextService.hasContext()) {
|
|
34585
|
+
throw new Error("getPositionHighestPnlCost requires a method context");
|
|
34586
|
+
}
|
|
34587
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34588
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34589
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34590
|
+
}
|
|
34591
|
+
/**
|
|
34592
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
34593
|
+
*
|
|
34594
|
+
* Returns null if no pending signal exists.
|
|
34595
|
+
*
|
|
34596
|
+
* @param symbol - Trading pair symbol
|
|
34597
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
34598
|
+
*
|
|
34599
|
+
* @example
|
|
34600
|
+
* ```typescript
|
|
34601
|
+
* import { getPositionHighestProfitBreakeven } from "backtest-kit";
|
|
34602
|
+
*
|
|
34603
|
+
* const couldBreakeven = await getPositionHighestProfitBreakeven("BTCUSDT");
|
|
34604
|
+
* // e.g. true (price reached the breakeven threshold at its peak)
|
|
34605
|
+
* ```
|
|
34606
|
+
*/
|
|
34607
|
+
async function getPositionHighestProfitBreakeven(symbol) {
|
|
34608
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME, { symbol });
|
|
34609
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34610
|
+
throw new Error("getPositionHighestProfitBreakeven requires an execution context");
|
|
34611
|
+
}
|
|
34612
|
+
if (!MethodContextService.hasContext()) {
|
|
34613
|
+
throw new Error("getPositionHighestProfitBreakeven requires a method context");
|
|
34614
|
+
}
|
|
34615
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34616
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34617
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34618
|
+
}
|
|
34619
|
+
/**
|
|
34620
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
34621
|
+
*
|
|
34622
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
34623
|
+
* Zero when called at the exact moment the peak was set.
|
|
34624
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
34625
|
+
*
|
|
34626
|
+
* Returns null if no pending signal exists.
|
|
34627
|
+
*
|
|
34628
|
+
* @param symbol - Trading pair symbol
|
|
34629
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
34630
|
+
*
|
|
34631
|
+
* @example
|
|
34632
|
+
* ```typescript
|
|
34633
|
+
* import { getPositionDrawdownMinutes } from "backtest-kit";
|
|
34634
|
+
*
|
|
34635
|
+
* const drawdown = await getPositionDrawdownMinutes("BTCUSDT");
|
|
34636
|
+
* // e.g. 30 (30 minutes since the highest profit price)
|
|
34637
|
+
* ```
|
|
34638
|
+
*/
|
|
34639
|
+
async function getPositionDrawdownMinutes(symbol) {
|
|
34640
|
+
bt.loggerService.info(GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
|
|
34641
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34642
|
+
throw new Error("getPositionDrawdownMinutes requires an execution context");
|
|
34643
|
+
}
|
|
34644
|
+
if (!MethodContextService.hasContext()) {
|
|
34645
|
+
throw new Error("getPositionDrawdownMinutes requires a method context");
|
|
34646
|
+
}
|
|
34647
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34648
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34649
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34650
|
+
}
|
|
33454
34651
|
/**
|
|
33455
34652
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
33456
34653
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -33628,6 +34825,8 @@ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
|
|
|
33628
34825
|
const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
|
|
33629
34826
|
const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
|
|
33630
34827
|
const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
|
|
34828
|
+
const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
|
|
34829
|
+
const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
|
|
33631
34830
|
/**
|
|
33632
34831
|
* Subscribes to all signal events with queued async processing.
|
|
33633
34832
|
*
|
|
@@ -34754,6 +35953,33 @@ function listenSyncOnce(filterFn, fn) {
|
|
|
34754
35953
|
bt.loggerService.log(LISTEN_SYNC_ONCE_METHOD_NAME);
|
|
34755
35954
|
return syncSubject.filter(filterFn).once(fn);
|
|
34756
35955
|
}
|
|
35956
|
+
/**
|
|
35957
|
+
* Subscribes to highest profit events with queued async processing.
|
|
35958
|
+
* Emits when a signal reaches a new highest profit level during its lifecycle.
|
|
35959
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
35960
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
35961
|
+
* Useful for tracking profit milestones and implementing dynamic management logic.
|
|
35962
|
+
*
|
|
35963
|
+
* @param fn - Callback function to handle highest profit events
|
|
35964
|
+
* @return Unsubscribe function to stop listening to events
|
|
35965
|
+
*/
|
|
35966
|
+
function listenHighestProfit(fn) {
|
|
35967
|
+
bt.loggerService.log(LISTEN_HIGHEST_PROFIT_METHOD_NAME);
|
|
35968
|
+
return highestProfitSubject.subscribe(queued(async (event) => fn(event)));
|
|
35969
|
+
}
|
|
35970
|
+
/**
|
|
35971
|
+
* Subscribes to filtered highest profit events with one-time execution.
|
|
35972
|
+
* Listens for events matching the filter predicate, then executes callback once
|
|
35973
|
+
* and automatically unsubscribes. Useful for waiting for specific profit conditions.
|
|
35974
|
+
*
|
|
35975
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
35976
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
35977
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
35978
|
+
*/
|
|
35979
|
+
function listenHighestProfitOnce(filterFn, fn) {
|
|
35980
|
+
bt.loggerService.log(LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME);
|
|
35981
|
+
return highestProfitSubject.filter(filterFn).once(fn);
|
|
35982
|
+
}
|
|
34757
35983
|
|
|
34758
35984
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
34759
35985
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
@@ -34767,7 +35993,7 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
|
|
|
34767
35993
|
const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
|
|
34768
35994
|
const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
|
|
34769
35995
|
const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
|
|
34770
|
-
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.
|
|
35996
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionEffectivePrice";
|
|
34771
35997
|
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
|
|
34772
35998
|
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
|
|
34773
35999
|
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
|
|
@@ -34775,6 +36001,14 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
|
|
|
34775
36001
|
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
34776
36002
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
34777
36003
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
|
|
36004
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
|
|
36005
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
|
|
36006
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
|
|
36007
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
|
|
36008
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
|
|
36009
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
|
|
36010
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
|
|
36011
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
|
|
34778
36012
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
34779
36013
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
34780
36014
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -35347,7 +36581,7 @@ class BacktestUtils {
|
|
|
35347
36581
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35348
36582
|
* @returns Effective entry price, or null if no active position
|
|
35349
36583
|
*/
|
|
35350
|
-
this.
|
|
36584
|
+
this.getPositionEffectivePrice = async (symbol, context) => {
|
|
35351
36585
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
35352
36586
|
symbol,
|
|
35353
36587
|
context,
|
|
@@ -35363,7 +36597,7 @@ class BacktestUtils {
|
|
|
35363
36597
|
actions &&
|
|
35364
36598
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
35365
36599
|
}
|
|
35366
|
-
return await bt.strategyCoreService.
|
|
36600
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
35367
36601
|
};
|
|
35368
36602
|
/**
|
|
35369
36603
|
* Returns the total number of base-asset units currently held in the position.
|
|
@@ -35581,6 +36815,230 @@ class BacktestUtils {
|
|
|
35581
36815
|
}
|
|
35582
36816
|
return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
|
|
35583
36817
|
};
|
|
36818
|
+
/**
|
|
36819
|
+
* Returns the original estimated duration for the current pending signal.
|
|
36820
|
+
*
|
|
36821
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
36822
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
36823
|
+
*
|
|
36824
|
+
* Returns null if no pending signal exists.
|
|
36825
|
+
*
|
|
36826
|
+
* @param symbol - Trading pair symbol
|
|
36827
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36828
|
+
* @returns Estimated duration in minutes, or null if no active position
|
|
36829
|
+
*/
|
|
36830
|
+
this.getPositionEstimateMinutes = async (symbol, context) => {
|
|
36831
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
|
|
36832
|
+
symbol,
|
|
36833
|
+
context,
|
|
36834
|
+
});
|
|
36835
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36836
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36837
|
+
{
|
|
36838
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36839
|
+
riskName &&
|
|
36840
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36841
|
+
riskList &&
|
|
36842
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
36843
|
+
actions &&
|
|
36844
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
36845
|
+
}
|
|
36846
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(true, symbol, context);
|
|
36847
|
+
};
|
|
36848
|
+
/**
|
|
36849
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
36850
|
+
*
|
|
36851
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
36852
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
36853
|
+
*
|
|
36854
|
+
* Returns null if no pending signal exists.
|
|
36855
|
+
*
|
|
36856
|
+
* @param symbol - Trading pair symbol
|
|
36857
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36858
|
+
* @returns Remaining minutes (≥ 0), or null if no active position
|
|
36859
|
+
*/
|
|
36860
|
+
this.getPositionCountdownMinutes = async (symbol, context) => {
|
|
36861
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
|
|
36862
|
+
symbol,
|
|
36863
|
+
context,
|
|
36864
|
+
});
|
|
36865
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36866
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36867
|
+
{
|
|
36868
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36869
|
+
riskName &&
|
|
36870
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36871
|
+
riskList &&
|
|
36872
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
36873
|
+
actions &&
|
|
36874
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
36875
|
+
}
|
|
36876
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
|
|
36877
|
+
};
|
|
36878
|
+
/**
|
|
36879
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
36880
|
+
*
|
|
36881
|
+
* Returns null if no pending signal exists.
|
|
36882
|
+
*
|
|
36883
|
+
* @param symbol - Trading pair symbol
|
|
36884
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36885
|
+
* @returns price or null if no active position
|
|
36886
|
+
*/
|
|
36887
|
+
this.getPositionHighestProfitPrice = async (symbol, context) => {
|
|
36888
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
|
|
36889
|
+
symbol,
|
|
36890
|
+
context,
|
|
36891
|
+
});
|
|
36892
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36893
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36894
|
+
{
|
|
36895
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36896
|
+
riskName &&
|
|
36897
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36898
|
+
riskList &&
|
|
36899
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
36900
|
+
actions &&
|
|
36901
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
36902
|
+
}
|
|
36903
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(true, symbol, context);
|
|
36904
|
+
};
|
|
36905
|
+
/**
|
|
36906
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
36907
|
+
*
|
|
36908
|
+
* Returns null if no pending signal exists.
|
|
36909
|
+
*
|
|
36910
|
+
* @param symbol - Trading pair symbol
|
|
36911
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36912
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
36913
|
+
*/
|
|
36914
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context) => {
|
|
36915
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
|
|
36916
|
+
symbol,
|
|
36917
|
+
context,
|
|
36918
|
+
});
|
|
36919
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36920
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36921
|
+
{
|
|
36922
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36923
|
+
riskName &&
|
|
36924
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36925
|
+
riskList &&
|
|
36926
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
36927
|
+
actions &&
|
|
36928
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
36929
|
+
}
|
|
36930
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(true, symbol, context);
|
|
36931
|
+
};
|
|
36932
|
+
/**
|
|
36933
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
36934
|
+
*
|
|
36935
|
+
* Returns null if no pending signal exists.
|
|
36936
|
+
*
|
|
36937
|
+
* @param symbol - Trading pair symbol
|
|
36938
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36939
|
+
* @returns PnL percentage or null if no active position
|
|
36940
|
+
*/
|
|
36941
|
+
this.getPositionHighestPnlPercentage = async (symbol, context) => {
|
|
36942
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
|
|
36943
|
+
symbol,
|
|
36944
|
+
context,
|
|
36945
|
+
});
|
|
36946
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36947
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36948
|
+
{
|
|
36949
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36950
|
+
riskName &&
|
|
36951
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36952
|
+
riskList &&
|
|
36953
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
36954
|
+
actions &&
|
|
36955
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
36956
|
+
}
|
|
36957
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(true, symbol, context);
|
|
36958
|
+
};
|
|
36959
|
+
/**
|
|
36960
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
36961
|
+
*
|
|
36962
|
+
* Returns null if no pending signal exists.
|
|
36963
|
+
*
|
|
36964
|
+
* @param symbol - Trading pair symbol
|
|
36965
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36966
|
+
* @returns PnL cost or null if no active position
|
|
36967
|
+
*/
|
|
36968
|
+
this.getPositionHighestPnlCost = async (symbol, context) => {
|
|
36969
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
|
|
36970
|
+
symbol,
|
|
36971
|
+
context,
|
|
36972
|
+
});
|
|
36973
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36974
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36975
|
+
{
|
|
36976
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36977
|
+
riskName &&
|
|
36978
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36979
|
+
riskList &&
|
|
36980
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
36981
|
+
actions &&
|
|
36982
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
36983
|
+
}
|
|
36984
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(true, symbol, context);
|
|
36985
|
+
};
|
|
36986
|
+
/**
|
|
36987
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
36988
|
+
*
|
|
36989
|
+
* @param symbol - Trading pair symbol
|
|
36990
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36991
|
+
* @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
|
|
36992
|
+
*/
|
|
36993
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context) => {
|
|
36994
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
|
|
36995
|
+
symbol,
|
|
36996
|
+
context,
|
|
36997
|
+
});
|
|
36998
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
36999
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
37000
|
+
{
|
|
37001
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37002
|
+
riskName &&
|
|
37003
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
37004
|
+
riskList &&
|
|
37005
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
37006
|
+
actions &&
|
|
37007
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
37008
|
+
}
|
|
37009
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(true, symbol, context);
|
|
37010
|
+
};
|
|
37011
|
+
/**
|
|
37012
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
37013
|
+
*
|
|
37014
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
37015
|
+
* Zero when called at the exact moment the peak was set.
|
|
37016
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
37017
|
+
*
|
|
37018
|
+
* Returns null if no pending signal exists.
|
|
37019
|
+
*
|
|
37020
|
+
* @param symbol - Trading pair symbol
|
|
37021
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
37022
|
+
* @returns Drawdown duration in minutes, or null if no active position
|
|
37023
|
+
*/
|
|
37024
|
+
this.getPositionDrawdownMinutes = async (symbol, context) => {
|
|
37025
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
|
|
37026
|
+
symbol,
|
|
37027
|
+
context,
|
|
37028
|
+
});
|
|
37029
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37030
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37031
|
+
{
|
|
37032
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37033
|
+
riskName &&
|
|
37034
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37035
|
+
riskList &&
|
|
37036
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
37037
|
+
actions &&
|
|
37038
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
37039
|
+
}
|
|
37040
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
|
|
37041
|
+
};
|
|
35584
37042
|
/**
|
|
35585
37043
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
35586
37044
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -36142,7 +37600,7 @@ class BacktestUtils {
|
|
|
36142
37600
|
if (!signal) {
|
|
36143
37601
|
return false;
|
|
36144
37602
|
}
|
|
36145
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37603
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36146
37604
|
if (effectivePriceOpen === null) {
|
|
36147
37605
|
return false;
|
|
36148
37606
|
}
|
|
@@ -36227,7 +37685,7 @@ class BacktestUtils {
|
|
|
36227
37685
|
if (!signal) {
|
|
36228
37686
|
return false;
|
|
36229
37687
|
}
|
|
36230
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37688
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36231
37689
|
if (effectivePriceOpen === null) {
|
|
36232
37690
|
return false;
|
|
36233
37691
|
}
|
|
@@ -36280,7 +37738,7 @@ class BacktestUtils {
|
|
|
36280
37738
|
if (!signal) {
|
|
36281
37739
|
return false;
|
|
36282
37740
|
}
|
|
36283
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37741
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36284
37742
|
if (effectivePriceOpen === null) {
|
|
36285
37743
|
return false;
|
|
36286
37744
|
}
|
|
@@ -36334,7 +37792,7 @@ class BacktestUtils {
|
|
|
36334
37792
|
if (!signal) {
|
|
36335
37793
|
return false;
|
|
36336
37794
|
}
|
|
36337
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37795
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36338
37796
|
if (effectivePriceOpen === null) {
|
|
36339
37797
|
return false;
|
|
36340
37798
|
}
|
|
@@ -36396,7 +37854,7 @@ class BacktestUtils {
|
|
|
36396
37854
|
if (!signal) {
|
|
36397
37855
|
return false;
|
|
36398
37856
|
}
|
|
36399
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37857
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36400
37858
|
if (effectivePriceOpen === null) {
|
|
36401
37859
|
return false;
|
|
36402
37860
|
}
|
|
@@ -36684,7 +38142,7 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
|
|
|
36684
38142
|
const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
|
|
36685
38143
|
const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
|
|
36686
38144
|
const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
|
|
36687
|
-
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.
|
|
38145
|
+
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionEffectivePrice";
|
|
36688
38146
|
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
|
|
36689
38147
|
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
|
|
36690
38148
|
const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
|
|
@@ -36692,6 +38150,14 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
|
36692
38150
|
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
36693
38151
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
36694
38152
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
|
|
38153
|
+
const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
|
|
38154
|
+
const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
|
|
38155
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
|
|
38156
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
|
|
38157
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
|
|
38158
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
|
|
38159
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
|
|
38160
|
+
const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
|
|
36695
38161
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
36696
38162
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
36697
38163
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -37295,7 +38761,7 @@ class LiveUtils {
|
|
|
37295
38761
|
* @param context - Execution context with strategyName and exchangeName
|
|
37296
38762
|
* @returns Effective entry price, or null if no active position
|
|
37297
38763
|
*/
|
|
37298
|
-
this.
|
|
38764
|
+
this.getPositionEffectivePrice = async (symbol, context) => {
|
|
37299
38765
|
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
37300
38766
|
symbol,
|
|
37301
38767
|
context,
|
|
@@ -37311,7 +38777,7 @@ class LiveUtils {
|
|
|
37311
38777
|
actions &&
|
|
37312
38778
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
37313
38779
|
}
|
|
37314
|
-
return await bt.strategyCoreService.
|
|
38780
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
37315
38781
|
strategyName: context.strategyName,
|
|
37316
38782
|
exchangeName: context.exchangeName,
|
|
37317
38783
|
frameName: "",
|
|
@@ -37561,6 +39027,262 @@ class LiveUtils {
|
|
|
37561
39027
|
frameName: "",
|
|
37562
39028
|
});
|
|
37563
39029
|
};
|
|
39030
|
+
/**
|
|
39031
|
+
* Returns the original estimated duration for the current pending signal.
|
|
39032
|
+
*
|
|
39033
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
39034
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
39035
|
+
*
|
|
39036
|
+
* Returns null if no pending signal exists.
|
|
39037
|
+
*
|
|
39038
|
+
* @param symbol - Trading pair symbol
|
|
39039
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39040
|
+
* @returns Estimated duration in minutes, or null if no active position
|
|
39041
|
+
*/
|
|
39042
|
+
this.getPositionEstimateMinutes = async (symbol, context) => {
|
|
39043
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
|
|
39044
|
+
symbol,
|
|
39045
|
+
context,
|
|
39046
|
+
});
|
|
39047
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39048
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39049
|
+
{
|
|
39050
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39051
|
+
riskName &&
|
|
39052
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39053
|
+
riskList &&
|
|
39054
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
39055
|
+
actions &&
|
|
39056
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
39057
|
+
}
|
|
39058
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(false, symbol, {
|
|
39059
|
+
strategyName: context.strategyName,
|
|
39060
|
+
exchangeName: context.exchangeName,
|
|
39061
|
+
frameName: "",
|
|
39062
|
+
});
|
|
39063
|
+
};
|
|
39064
|
+
/**
|
|
39065
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
39066
|
+
*
|
|
39067
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
39068
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
39069
|
+
*
|
|
39070
|
+
* Returns null if no pending signal exists.
|
|
39071
|
+
*
|
|
39072
|
+
* @param symbol - Trading pair symbol
|
|
39073
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39074
|
+
* @returns Remaining minutes (≥ 0), or null if no active position
|
|
39075
|
+
*/
|
|
39076
|
+
this.getPositionCountdownMinutes = async (symbol, context) => {
|
|
39077
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
|
|
39078
|
+
symbol,
|
|
39079
|
+
context,
|
|
39080
|
+
});
|
|
39081
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39082
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39083
|
+
{
|
|
39084
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39085
|
+
riskName &&
|
|
39086
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39087
|
+
riskList &&
|
|
39088
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
39089
|
+
actions &&
|
|
39090
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
39091
|
+
}
|
|
39092
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(false, symbol, {
|
|
39093
|
+
strategyName: context.strategyName,
|
|
39094
|
+
exchangeName: context.exchangeName,
|
|
39095
|
+
frameName: "",
|
|
39096
|
+
});
|
|
39097
|
+
};
|
|
39098
|
+
/**
|
|
39099
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
39100
|
+
*
|
|
39101
|
+
* Returns null if no pending signal exists.
|
|
39102
|
+
*
|
|
39103
|
+
* @param symbol - Trading pair symbol
|
|
39104
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39105
|
+
* @returns price or null if no active position
|
|
39106
|
+
*/
|
|
39107
|
+
this.getPositionHighestProfitPrice = async (symbol, context) => {
|
|
39108
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
|
|
39109
|
+
symbol,
|
|
39110
|
+
context,
|
|
39111
|
+
});
|
|
39112
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39113
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39114
|
+
{
|
|
39115
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39116
|
+
riskName &&
|
|
39117
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39118
|
+
riskList &&
|
|
39119
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
39120
|
+
actions &&
|
|
39121
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
39122
|
+
}
|
|
39123
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(false, symbol, {
|
|
39124
|
+
strategyName: context.strategyName,
|
|
39125
|
+
exchangeName: context.exchangeName,
|
|
39126
|
+
frameName: "",
|
|
39127
|
+
});
|
|
39128
|
+
};
|
|
39129
|
+
/**
|
|
39130
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
39131
|
+
*
|
|
39132
|
+
* Returns null if no pending signal exists.
|
|
39133
|
+
*
|
|
39134
|
+
* @param symbol - Trading pair symbol
|
|
39135
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39136
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
39137
|
+
*/
|
|
39138
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context) => {
|
|
39139
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
|
|
39140
|
+
symbol,
|
|
39141
|
+
context,
|
|
39142
|
+
});
|
|
39143
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39144
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39145
|
+
{
|
|
39146
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39147
|
+
riskName &&
|
|
39148
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39149
|
+
riskList &&
|
|
39150
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
39151
|
+
actions &&
|
|
39152
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
39153
|
+
}
|
|
39154
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(false, symbol, {
|
|
39155
|
+
strategyName: context.strategyName,
|
|
39156
|
+
exchangeName: context.exchangeName,
|
|
39157
|
+
frameName: "",
|
|
39158
|
+
});
|
|
39159
|
+
};
|
|
39160
|
+
/**
|
|
39161
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
39162
|
+
*
|
|
39163
|
+
* Returns null if no pending signal exists.
|
|
39164
|
+
*
|
|
39165
|
+
* @param symbol - Trading pair symbol
|
|
39166
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39167
|
+
* @returns PnL percentage or null if no active position
|
|
39168
|
+
*/
|
|
39169
|
+
this.getPositionHighestPnlPercentage = async (symbol, context) => {
|
|
39170
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
|
|
39171
|
+
symbol,
|
|
39172
|
+
context,
|
|
39173
|
+
});
|
|
39174
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39175
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39176
|
+
{
|
|
39177
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39178
|
+
riskName &&
|
|
39179
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39180
|
+
riskList &&
|
|
39181
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
39182
|
+
actions &&
|
|
39183
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
39184
|
+
}
|
|
39185
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(false, symbol, {
|
|
39186
|
+
strategyName: context.strategyName,
|
|
39187
|
+
exchangeName: context.exchangeName,
|
|
39188
|
+
frameName: "",
|
|
39189
|
+
});
|
|
39190
|
+
};
|
|
39191
|
+
/**
|
|
39192
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
39193
|
+
*
|
|
39194
|
+
* Returns null if no pending signal exists.
|
|
39195
|
+
*
|
|
39196
|
+
* @param symbol - Trading pair symbol
|
|
39197
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39198
|
+
* @returns PnL cost or null if no active position
|
|
39199
|
+
*/
|
|
39200
|
+
this.getPositionHighestPnlCost = async (symbol, context) => {
|
|
39201
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
|
|
39202
|
+
symbol,
|
|
39203
|
+
context,
|
|
39204
|
+
});
|
|
39205
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39206
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39207
|
+
{
|
|
39208
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39209
|
+
riskName &&
|
|
39210
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39211
|
+
riskList &&
|
|
39212
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
39213
|
+
actions &&
|
|
39214
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
39215
|
+
}
|
|
39216
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(false, symbol, {
|
|
39217
|
+
strategyName: context.strategyName,
|
|
39218
|
+
exchangeName: context.exchangeName,
|
|
39219
|
+
frameName: "",
|
|
39220
|
+
});
|
|
39221
|
+
};
|
|
39222
|
+
/**
|
|
39223
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
39224
|
+
*
|
|
39225
|
+
* @param symbol - Trading pair symbol
|
|
39226
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39227
|
+
* @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
|
|
39228
|
+
*/
|
|
39229
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context) => {
|
|
39230
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
|
|
39231
|
+
symbol,
|
|
39232
|
+
context,
|
|
39233
|
+
});
|
|
39234
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39235
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39236
|
+
{
|
|
39237
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39238
|
+
riskName &&
|
|
39239
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39240
|
+
riskList &&
|
|
39241
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
39242
|
+
actions &&
|
|
39243
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
39244
|
+
}
|
|
39245
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(false, symbol, {
|
|
39246
|
+
strategyName: context.strategyName,
|
|
39247
|
+
exchangeName: context.exchangeName,
|
|
39248
|
+
frameName: "",
|
|
39249
|
+
});
|
|
39250
|
+
};
|
|
39251
|
+
/**
|
|
39252
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
39253
|
+
*
|
|
39254
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
39255
|
+
* Zero when called at the exact moment the peak was set.
|
|
39256
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
39257
|
+
*
|
|
39258
|
+
* Returns null if no pending signal exists.
|
|
39259
|
+
*
|
|
39260
|
+
* @param symbol - Trading pair symbol
|
|
39261
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39262
|
+
* @returns Drawdown duration in minutes, or null if no active position
|
|
39263
|
+
*/
|
|
39264
|
+
this.getPositionDrawdownMinutes = async (symbol, context) => {
|
|
39265
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
|
|
39266
|
+
symbol,
|
|
39267
|
+
context,
|
|
39268
|
+
});
|
|
39269
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39270
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39271
|
+
{
|
|
39272
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39273
|
+
riskName &&
|
|
39274
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39275
|
+
riskList &&
|
|
39276
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
39277
|
+
actions &&
|
|
39278
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
39279
|
+
}
|
|
39280
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(false, symbol, {
|
|
39281
|
+
strategyName: context.strategyName,
|
|
39282
|
+
exchangeName: context.exchangeName,
|
|
39283
|
+
frameName: "",
|
|
39284
|
+
});
|
|
39285
|
+
};
|
|
37564
39286
|
/**
|
|
37565
39287
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
37566
39288
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38215,7 +39937,7 @@ class LiveUtils {
|
|
|
38215
39937
|
if (!signal) {
|
|
38216
39938
|
return false;
|
|
38217
39939
|
}
|
|
38218
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
39940
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38219
39941
|
strategyName: context.strategyName,
|
|
38220
39942
|
exchangeName: context.exchangeName,
|
|
38221
39943
|
frameName: "",
|
|
@@ -38315,7 +40037,7 @@ class LiveUtils {
|
|
|
38315
40037
|
if (!signal) {
|
|
38316
40038
|
return false;
|
|
38317
40039
|
}
|
|
38318
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40040
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38319
40041
|
strategyName: context.strategyName,
|
|
38320
40042
|
exchangeName: context.exchangeName,
|
|
38321
40043
|
frameName: "",
|
|
@@ -38384,7 +40106,7 @@ class LiveUtils {
|
|
|
38384
40106
|
if (!signal) {
|
|
38385
40107
|
return false;
|
|
38386
40108
|
}
|
|
38387
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40109
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38388
40110
|
strategyName: context.strategyName,
|
|
38389
40111
|
exchangeName: context.exchangeName,
|
|
38390
40112
|
frameName: "",
|
|
@@ -38454,7 +40176,7 @@ class LiveUtils {
|
|
|
38454
40176
|
if (!signal) {
|
|
38455
40177
|
return false;
|
|
38456
40178
|
}
|
|
38457
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40179
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38458
40180
|
strategyName: context.strategyName,
|
|
38459
40181
|
exchangeName: context.exchangeName,
|
|
38460
40182
|
frameName: "",
|
|
@@ -38532,7 +40254,7 @@ class LiveUtils {
|
|
|
38532
40254
|
if (!signal) {
|
|
38533
40255
|
return false;
|
|
38534
40256
|
}
|
|
38535
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40257
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38536
40258
|
strategyName: context.strategyName,
|
|
38537
40259
|
exchangeName: context.exchangeName,
|
|
38538
40260
|
frameName: "",
|
|
@@ -41956,6 +43678,98 @@ class PartialUtils {
|
|
|
41956
43678
|
*/
|
|
41957
43679
|
const Partial = new PartialUtils();
|
|
41958
43680
|
|
|
43681
|
+
const HIGHEST_PROFIT_METHOD_NAME_GET_DATA = "HighestProfitUtils.getData";
|
|
43682
|
+
const HIGHEST_PROFIT_METHOD_NAME_GET_REPORT = "HighestProfitUtils.getReport";
|
|
43683
|
+
const HIGHEST_PROFIT_METHOD_NAME_DUMP = "HighestProfitUtils.dump";
|
|
43684
|
+
/**
|
|
43685
|
+
* Utility class for accessing highest profit reports and statistics.
|
|
43686
|
+
*
|
|
43687
|
+
* Provides static-like methods (via singleton instance) to retrieve data
|
|
43688
|
+
* accumulated by HighestProfitMarkdownService from highestProfitSubject events.
|
|
43689
|
+
*
|
|
43690
|
+
* @example
|
|
43691
|
+
* ```typescript
|
|
43692
|
+
* import { HighestProfit } from "backtest-kit";
|
|
43693
|
+
*
|
|
43694
|
+
* const stats = await HighestProfit.getData("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43695
|
+
* const report = await HighestProfit.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43696
|
+
* await HighestProfit.dump("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43697
|
+
* ```
|
|
43698
|
+
*/
|
|
43699
|
+
class HighestProfitUtils {
|
|
43700
|
+
constructor() {
|
|
43701
|
+
/**
|
|
43702
|
+
* Retrieves statistical data from accumulated highest profit events.
|
|
43703
|
+
*
|
|
43704
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43705
|
+
* @param context - Execution context
|
|
43706
|
+
* @param backtest - Whether to query backtest data
|
|
43707
|
+
* @returns Promise resolving to HighestProfitStatisticsModel
|
|
43708
|
+
*/
|
|
43709
|
+
this.getData = async (symbol, context, backtest = false) => {
|
|
43710
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
43711
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43712
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43713
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43714
|
+
{
|
|
43715
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43716
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43717
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
|
|
43718
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
|
|
43719
|
+
}
|
|
43720
|
+
return await bt.highestProfitMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
43721
|
+
};
|
|
43722
|
+
/**
|
|
43723
|
+
* Generates a markdown report with all highest profit events for a symbol-strategy pair.
|
|
43724
|
+
*
|
|
43725
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43726
|
+
* @param context - Execution context
|
|
43727
|
+
* @param backtest - Whether to query backtest data
|
|
43728
|
+
* @param columns - Optional column configuration
|
|
43729
|
+
* @returns Promise resolving to markdown formatted report string
|
|
43730
|
+
*/
|
|
43731
|
+
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
43732
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
43733
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43734
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43735
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43736
|
+
{
|
|
43737
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43738
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43739
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
|
|
43740
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
|
|
43741
|
+
}
|
|
43742
|
+
return await bt.highestProfitMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
|
|
43743
|
+
};
|
|
43744
|
+
/**
|
|
43745
|
+
* Generates and saves a markdown report to file.
|
|
43746
|
+
*
|
|
43747
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43748
|
+
* @param context - Execution context
|
|
43749
|
+
* @param backtest - Whether to query backtest data
|
|
43750
|
+
* @param path - Output directory path (default: "./dump/highest_profit")
|
|
43751
|
+
* @param columns - Optional column configuration
|
|
43752
|
+
*/
|
|
43753
|
+
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
43754
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
43755
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43756
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43757
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43758
|
+
{
|
|
43759
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43760
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43761
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
|
|
43762
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
|
|
43763
|
+
}
|
|
43764
|
+
await bt.highestProfitMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
|
|
43765
|
+
};
|
|
43766
|
+
}
|
|
43767
|
+
}
|
|
43768
|
+
/**
|
|
43769
|
+
* Global singleton instance of HighestProfitUtils.
|
|
43770
|
+
*/
|
|
43771
|
+
const HighestProfit = new HighestProfitUtils();
|
|
43772
|
+
|
|
41959
43773
|
/**
|
|
41960
43774
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
41961
43775
|
*
|
|
@@ -46423,4 +48237,4 @@ const percentValue = (yesterdayValue, todayValue) => {
|
|
|
46423
48237
|
return yesterdayValue / todayValue - 1;
|
|
46424
48238
|
};
|
|
46425
48239
|
|
|
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,
|
|
48240
|
+
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 };
|