backtest-kit 5.5.3 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +1979 -168
- package/build/index.mjs +1967 -168
- package/package.json +1 -1
- package/types.d.ts +1100 -140
package/build/index.cjs
CHANGED
|
@@ -127,6 +127,7 @@ const markdownServices$1 = {
|
|
|
127
127
|
riskMarkdownService: Symbol('riskMarkdownService'),
|
|
128
128
|
strategyMarkdownService: Symbol('strategyMarkdownService'),
|
|
129
129
|
syncMarkdownService: Symbol('syncMarkdownService'),
|
|
130
|
+
highestProfitMarkdownService: Symbol('highestProfitMarkdownService'),
|
|
130
131
|
};
|
|
131
132
|
const reportServices$1 = {
|
|
132
133
|
backtestReportService: Symbol('backtestReportService'),
|
|
@@ -140,6 +141,7 @@ const reportServices$1 = {
|
|
|
140
141
|
riskReportService: Symbol('riskReportService'),
|
|
141
142
|
strategyReportService: Symbol('strategyReportService'),
|
|
142
143
|
syncReportService: Symbol('syncReportService'),
|
|
144
|
+
highestProfitReportService: Symbol('highestProfitReportService'),
|
|
143
145
|
};
|
|
144
146
|
const validationServices$1 = {
|
|
145
147
|
exchangeValidationService: Symbol('exchangeValidationService'),
|
|
@@ -642,6 +644,12 @@ const strategyCommitSubject = new functoolsKit.Subject();
|
|
|
642
644
|
* BacktestLogicPrivateService::*run
|
|
643
645
|
*/
|
|
644
646
|
const backtestScheduleOpenSubject = new functoolsKit.Subject();
|
|
647
|
+
/**
|
|
648
|
+
* Highest profit emitter for real-time profit tracking.
|
|
649
|
+
* Emits updates on the highest profit achieved for an open position.
|
|
650
|
+
* Allows users to track profit milestones and implement custom management logic based on profit levels.
|
|
651
|
+
*/
|
|
652
|
+
const highestProfitSubject = new functoolsKit.Subject();
|
|
645
653
|
|
|
646
654
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
647
655
|
__proto__: null,
|
|
@@ -653,6 +661,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
653
661
|
doneWalkerSubject: doneWalkerSubject,
|
|
654
662
|
errorEmitter: errorEmitter,
|
|
655
663
|
exitEmitter: exitEmitter,
|
|
664
|
+
highestProfitSubject: highestProfitSubject,
|
|
656
665
|
partialLossSubject: partialLossSubject,
|
|
657
666
|
partialProfitSubject: partialProfitSubject,
|
|
658
667
|
performanceEmitter: performanceEmitter,
|
|
@@ -4271,6 +4280,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4271
4280
|
timestamp: currentTime,
|
|
4272
4281
|
_isScheduled: false,
|
|
4273
4282
|
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4283
|
+
_peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4274
4284
|
};
|
|
4275
4285
|
// Валидируем сигнал перед возвратом
|
|
4276
4286
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -4295,6 +4305,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4295
4305
|
timestamp: currentTime,
|
|
4296
4306
|
_isScheduled: true,
|
|
4297
4307
|
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4308
|
+
_peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4298
4309
|
};
|
|
4299
4310
|
// Валидируем сигнал перед возвратом
|
|
4300
4311
|
VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
|
|
@@ -4315,6 +4326,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4315
4326
|
timestamp: currentTime,
|
|
4316
4327
|
_isScheduled: false,
|
|
4317
4328
|
_entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4329
|
+
_peak: { price: currentPrice, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4318
4330
|
};
|
|
4319
4331
|
// Валидируем сигнал перед возвратом
|
|
4320
4332
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -5017,6 +5029,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
5017
5029
|
...scheduled,
|
|
5018
5030
|
pendingAt: activationTime,
|
|
5019
5031
|
_isScheduled: false,
|
|
5032
|
+
_peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
5020
5033
|
};
|
|
5021
5034
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
5022
5035
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
|
|
@@ -5653,7 +5666,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
|
|
|
5653
5666
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
5654
5667
|
return result;
|
|
5655
5668
|
};
|
|
5656
|
-
const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
5669
|
+
const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backtest) => {
|
|
5657
5670
|
let percentTp = 0;
|
|
5658
5671
|
let percentSl = 0;
|
|
5659
5672
|
const currentTime = self.params.execution.context.when.getTime();
|
|
@@ -5674,6 +5687,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5674
5687
|
const tpDistance = effectiveTakeProfit - effectivePriceOpen;
|
|
5675
5688
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5676
5689
|
percentTp = Math.min(progressPercent, 100);
|
|
5690
|
+
if (currentPrice > signal._peak.price) {
|
|
5691
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
|
|
5692
|
+
signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
5693
|
+
if (self.params.callbacks?.onWrite) {
|
|
5694
|
+
self.params.callbacks.onWrite(signal.symbol, signal, backtest);
|
|
5695
|
+
}
|
|
5696
|
+
!backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
5697
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
|
|
5698
|
+
}
|
|
5677
5699
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
5678
5700
|
}
|
|
5679
5701
|
else if (currentDistance < 0) {
|
|
@@ -5698,6 +5720,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5698
5720
|
const tpDistance = effectivePriceOpen - effectiveTakeProfit;
|
|
5699
5721
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5700
5722
|
percentTp = Math.min(progressPercent, 100);
|
|
5723
|
+
if (currentPrice < signal._peak.price) {
|
|
5724
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
|
|
5725
|
+
signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
5726
|
+
if (self.params.callbacks?.onWrite) {
|
|
5727
|
+
self.params.callbacks.onWrite(signal.symbol, signal, backtest);
|
|
5728
|
+
}
|
|
5729
|
+
!backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
5730
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
|
|
5731
|
+
}
|
|
5701
5732
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
5702
5733
|
}
|
|
5703
5734
|
if (currentDistance < 0) {
|
|
@@ -5745,7 +5776,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
|
|
|
5745
5776
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
|
|
5746
5777
|
return result;
|
|
5747
5778
|
};
|
|
5748
|
-
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason) => {
|
|
5779
|
+
const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
|
|
5749
5780
|
self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
|
|
5750
5781
|
symbol: self.params.execution.context.symbol,
|
|
5751
5782
|
signalId: scheduled.id,
|
|
@@ -5755,6 +5786,23 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
5755
5786
|
reason,
|
|
5756
5787
|
});
|
|
5757
5788
|
await self.setScheduledSignal(null);
|
|
5789
|
+
if (reason === "user") {
|
|
5790
|
+
await CALL_COMMIT_FN(self, {
|
|
5791
|
+
action: "cancel-scheduled",
|
|
5792
|
+
symbol: self.params.execution.context.symbol,
|
|
5793
|
+
strategyName: self.params.strategyName,
|
|
5794
|
+
exchangeName: self.params.exchangeName,
|
|
5795
|
+
frameName: self.params.frameName,
|
|
5796
|
+
signalId: scheduled.id,
|
|
5797
|
+
backtest: true,
|
|
5798
|
+
cancelId,
|
|
5799
|
+
timestamp: closeTimestamp,
|
|
5800
|
+
totalEntries: scheduled._entry?.length ?? 1,
|
|
5801
|
+
totalPartials: scheduled._partial?.length ?? 0,
|
|
5802
|
+
originalPriceOpen: scheduled.priceOpen,
|
|
5803
|
+
pnl: toProfitLossDto(scheduled, averagePrice),
|
|
5804
|
+
});
|
|
5805
|
+
}
|
|
5758
5806
|
await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
|
|
5759
5807
|
const result = {
|
|
5760
5808
|
action: "cancelled",
|
|
@@ -5767,6 +5815,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
|
|
|
5767
5815
|
symbol: self.params.execution.context.symbol,
|
|
5768
5816
|
backtest: self.params.execution.context.backtest,
|
|
5769
5817
|
reason,
|
|
5818
|
+
cancelId,
|
|
5770
5819
|
createdAt: closeTimestamp,
|
|
5771
5820
|
};
|
|
5772
5821
|
await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
|
|
@@ -5806,6 +5855,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
5806
5855
|
...scheduled,
|
|
5807
5856
|
pendingAt: activationTime,
|
|
5808
5857
|
_isScheduled: false,
|
|
5858
|
+
_peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
5809
5859
|
};
|
|
5810
5860
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
5811
5861
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
|
|
@@ -5955,7 +6005,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
|
|
|
5955
6005
|
// КРИТИЧНО: Проверяем был ли сигнал отменен пользователем через cancel()
|
|
5956
6006
|
if (self._cancelledSignal) {
|
|
5957
6007
|
// Сигнал был отменен через cancel() в onSchedulePing
|
|
5958
|
-
const
|
|
6008
|
+
const cancelId = self._cancelledSignal.cancelId;
|
|
6009
|
+
const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId);
|
|
5959
6010
|
return { outcome: "cancelled", result };
|
|
5960
6011
|
}
|
|
5961
6012
|
// КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
|
|
@@ -5985,6 +6036,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
|
|
|
5985
6036
|
...activatedSignal,
|
|
5986
6037
|
pendingAt: candle.timestamp,
|
|
5987
6038
|
_isScheduled: false,
|
|
6039
|
+
_peak: { price: activatedSignal.priceOpen, timestamp: candle.timestamp, pnlPercentage: 0, pnlCost: 0 },
|
|
5988
6040
|
};
|
|
5989
6041
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
5990
6042
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(candle.timestamp, pendingSignal.priceOpen, pendingSignal, self);
|
|
@@ -6186,6 +6238,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
6186
6238
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
6187
6239
|
const tpDistance = effectiveTakeProfit - effectivePriceOpen;
|
|
6188
6240
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
6241
|
+
if (averagePrice > signal._peak.price) {
|
|
6242
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
|
|
6243
|
+
signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6244
|
+
if (self.params.callbacks?.onWrite) {
|
|
6245
|
+
self.params.callbacks.onWrite(signal.symbol, signal, true);
|
|
6246
|
+
}
|
|
6247
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
|
|
6248
|
+
}
|
|
6189
6249
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
6190
6250
|
}
|
|
6191
6251
|
else if (currentDistance < 0) {
|
|
@@ -6208,6 +6268,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
6208
6268
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
6209
6269
|
const tpDistance = effectivePriceOpen - effectiveTakeProfit;
|
|
6210
6270
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
6271
|
+
if (averagePrice < signal._peak.price) {
|
|
6272
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
|
|
6273
|
+
signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6274
|
+
if (self.params.callbacks?.onWrite) {
|
|
6275
|
+
self.params.callbacks.onWrite(signal.symbol, signal, true);
|
|
6276
|
+
}
|
|
6277
|
+
await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
|
|
6278
|
+
}
|
|
6211
6279
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
6212
6280
|
}
|
|
6213
6281
|
if (currentDistance < 0) {
|
|
@@ -6584,8 +6652,8 @@ class ClientStrategy {
|
|
|
6584
6652
|
* @param symbol - Trading pair symbol
|
|
6585
6653
|
* @returns Promise resolving to effective entry price or null
|
|
6586
6654
|
*/
|
|
6587
|
-
async
|
|
6588
|
-
this.params.logger.debug("ClientStrategy
|
|
6655
|
+
async getPositionEffectivePrice(symbol) {
|
|
6656
|
+
this.params.logger.debug("ClientStrategy getPositionEffectivePrice", { symbol });
|
|
6589
6657
|
if (!this._pendingSignal) {
|
|
6590
6658
|
return null;
|
|
6591
6659
|
}
|
|
@@ -6742,6 +6810,170 @@ class ClientStrategy {
|
|
|
6742
6810
|
}
|
|
6743
6811
|
return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
|
|
6744
6812
|
}
|
|
6813
|
+
/**
|
|
6814
|
+
* Returns the original estimated duration for the current pending signal.
|
|
6815
|
+
*
|
|
6816
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
6817
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
6818
|
+
*
|
|
6819
|
+
* Returns null if no pending signal exists.
|
|
6820
|
+
*
|
|
6821
|
+
* @param symbol - Trading pair symbol
|
|
6822
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
6823
|
+
*/
|
|
6824
|
+
async getPositionEstimateMinutes(symbol) {
|
|
6825
|
+
this.params.logger.debug("ClientStrategy getPositionEstimateMinutes", { symbol });
|
|
6826
|
+
if (!this._pendingSignal) {
|
|
6827
|
+
return null;
|
|
6828
|
+
}
|
|
6829
|
+
return this._pendingSignal.minuteEstimatedTime;
|
|
6830
|
+
}
|
|
6831
|
+
/**
|
|
6832
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
6833
|
+
*
|
|
6834
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
6835
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
6836
|
+
*
|
|
6837
|
+
* Returns null if no pending signal exists.
|
|
6838
|
+
*
|
|
6839
|
+
* @param symbol - Trading pair symbol
|
|
6840
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
6841
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
6842
|
+
*/
|
|
6843
|
+
async getPositionCountdownMinutes(symbol, timestamp) {
|
|
6844
|
+
this.params.logger.debug("ClientStrategy getPositionCountdownMinutes", { symbol });
|
|
6845
|
+
if (!this._pendingSignal) {
|
|
6846
|
+
return null;
|
|
6847
|
+
}
|
|
6848
|
+
const elapsed = Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
|
|
6849
|
+
return Math.max(0, this._pendingSignal.minuteEstimatedTime - elapsed);
|
|
6850
|
+
}
|
|
6851
|
+
/**
|
|
6852
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
6853
|
+
*
|
|
6854
|
+
* Initialized at position open with the entry price and timestamp.
|
|
6855
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
6856
|
+
* - LONG: tracks the highest price seen above effective entry
|
|
6857
|
+
* - SHORT: tracks the lowest price seen below effective entry
|
|
6858
|
+
*
|
|
6859
|
+
* Returns null if no pending signal exists.
|
|
6860
|
+
* Never returns null when a signal is active — always contains at least the entry price.
|
|
6861
|
+
*
|
|
6862
|
+
* @param symbol - Trading pair symbol
|
|
6863
|
+
* @returns Promise resolving to price or null
|
|
6864
|
+
*/
|
|
6865
|
+
async getPositionHighestProfitPrice(symbol) {
|
|
6866
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitPrice", { symbol });
|
|
6867
|
+
if (!this._pendingSignal) {
|
|
6868
|
+
return null;
|
|
6869
|
+
}
|
|
6870
|
+
return this._pendingSignal._peak.price;
|
|
6871
|
+
}
|
|
6872
|
+
/**
|
|
6873
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
6874
|
+
*
|
|
6875
|
+
* Returns null if no pending signal exists.
|
|
6876
|
+
*
|
|
6877
|
+
* @param symbol - Trading pair symbol
|
|
6878
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
6879
|
+
*/
|
|
6880
|
+
async getPositionHighestProfitTimestamp(symbol) {
|
|
6881
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitTimestamp", { symbol });
|
|
6882
|
+
if (!this._pendingSignal) {
|
|
6883
|
+
return null;
|
|
6884
|
+
}
|
|
6885
|
+
return this._pendingSignal._peak.timestamp;
|
|
6886
|
+
}
|
|
6887
|
+
/**
|
|
6888
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
6889
|
+
*
|
|
6890
|
+
* Initialized at position open with 0.
|
|
6891
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
6892
|
+
* - LONG: tracks the PnL percentage at the highest price seen above effective entry
|
|
6893
|
+
* - SHORT: tracks the PnL percentage at the lowest price seen below effective entry
|
|
6894
|
+
*
|
|
6895
|
+
* Returns null if no pending signal exists.
|
|
6896
|
+
* Never returns null when a signal is active — always contains at least 0.
|
|
6897
|
+
*
|
|
6898
|
+
* @param symbol - Trading pair symbol
|
|
6899
|
+
* @returns Promise resolving to PnL percentage or null
|
|
6900
|
+
*/
|
|
6901
|
+
async getPositionHighestPnlPercentage(symbol) {
|
|
6902
|
+
this.params.logger.debug("ClientStrategy getPositionHighestPnlPercentage", { symbol });
|
|
6903
|
+
if (!this._pendingSignal) {
|
|
6904
|
+
return null;
|
|
6905
|
+
}
|
|
6906
|
+
return this._pendingSignal._peak.pnlPercentage;
|
|
6907
|
+
}
|
|
6908
|
+
/**
|
|
6909
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
6910
|
+
*
|
|
6911
|
+
* Initialized at position open with 0.
|
|
6912
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
6913
|
+
* - LONG: tracks the PnL cost at the highest price seen above effective entry
|
|
6914
|
+
* - SHORT: tracks the PnL cost at the lowest price seen below effective entry
|
|
6915
|
+
*
|
|
6916
|
+
* Returns null if no pending signal exists.
|
|
6917
|
+
* Never returns null when a signal is active — always contains at least 0.
|
|
6918
|
+
*
|
|
6919
|
+
* @param symbol - Trading pair symbol
|
|
6920
|
+
* @returns Promise resolving to PnL cost or null
|
|
6921
|
+
*/
|
|
6922
|
+
async getPositionHighestPnlCost(symbol) {
|
|
6923
|
+
this.params.logger.debug("ClientStrategy getPositionHighestPnlCost", { symbol });
|
|
6924
|
+
if (!this._pendingSignal) {
|
|
6925
|
+
return null;
|
|
6926
|
+
}
|
|
6927
|
+
return this._pendingSignal._peak.pnlCost;
|
|
6928
|
+
}
|
|
6929
|
+
/**
|
|
6930
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
6931
|
+
*
|
|
6932
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
6933
|
+
* Zero when called at the exact moment the peak was set.
|
|
6934
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
6935
|
+
*
|
|
6936
|
+
* Returns null if no pending signal exists.
|
|
6937
|
+
*
|
|
6938
|
+
* @param symbol - Trading pair symbol
|
|
6939
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
6940
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
6941
|
+
*/
|
|
6942
|
+
async getPositionHighestProfitBreakeven(symbol) {
|
|
6943
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitBreakeven", { symbol });
|
|
6944
|
+
if (!this._pendingSignal) {
|
|
6945
|
+
return null;
|
|
6946
|
+
}
|
|
6947
|
+
const signal = this._pendingSignal;
|
|
6948
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
6949
|
+
const peakPrice = signal._peak.price;
|
|
6950
|
+
const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2 + GLOBAL_CONFIG.CC_BREAKEVEN_THRESHOLD;
|
|
6951
|
+
if (signal.position === "long") {
|
|
6952
|
+
return peakPrice >= effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
|
|
6953
|
+
}
|
|
6954
|
+
else {
|
|
6955
|
+
return peakPrice <= effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
/**
|
|
6959
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
6960
|
+
*
|
|
6961
|
+
* Measures how long the position has been pulling back from its peak.
|
|
6962
|
+
* Zero when called at the exact moment the peak was set.
|
|
6963
|
+
*
|
|
6964
|
+
* Returns null if no pending signal exists.
|
|
6965
|
+
*
|
|
6966
|
+
* @param symbol - Trading pair symbol
|
|
6967
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
6968
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
6969
|
+
*/
|
|
6970
|
+
async getPositionDrawdownMinutes(symbol, timestamp) {
|
|
6971
|
+
this.params.logger.debug("ClientStrategy getPositionDrawdownMinutes", { symbol });
|
|
6972
|
+
if (!this._pendingSignal) {
|
|
6973
|
+
return null;
|
|
6974
|
+
}
|
|
6975
|
+
return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
|
|
6976
|
+
}
|
|
6745
6977
|
/**
|
|
6746
6978
|
* Performs a single tick of strategy execution.
|
|
6747
6979
|
*
|
|
@@ -6917,6 +7149,7 @@ class ClientStrategy {
|
|
|
6917
7149
|
...activatedSignal,
|
|
6918
7150
|
pendingAt: currentTime,
|
|
6919
7151
|
_isScheduled: false,
|
|
7152
|
+
_peak: { price: activatedSignal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
6920
7153
|
};
|
|
6921
7154
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(currentTime, currentPrice, pendingSignal, this);
|
|
6922
7155
|
if (!syncOpenAllowed) {
|
|
@@ -7039,7 +7272,7 @@ class ClientStrategy {
|
|
|
7039
7272
|
if (closedResult) {
|
|
7040
7273
|
return closedResult;
|
|
7041
7274
|
}
|
|
7042
|
-
return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice);
|
|
7275
|
+
return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice, false);
|
|
7043
7276
|
}
|
|
7044
7277
|
/**
|
|
7045
7278
|
* Fast backtests a signal using historical candle data.
|
|
@@ -9026,7 +9259,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
9026
9259
|
* @param backtest - Whether running in backtest mode
|
|
9027
9260
|
* @returns Unique string key for memoization
|
|
9028
9261
|
*/
|
|
9029
|
-
const CREATE_KEY_FN$
|
|
9262
|
+
const CREATE_KEY_FN$p = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9030
9263
|
const parts = [symbol, strategyName, exchangeName];
|
|
9031
9264
|
if (frameName)
|
|
9032
9265
|
parts.push(frameName);
|
|
@@ -9150,6 +9383,43 @@ const CREATE_COMMIT_FN = (self) => functoolsKit.trycatch(async (event) => {
|
|
|
9150
9383
|
},
|
|
9151
9384
|
defaultValue: null,
|
|
9152
9385
|
});
|
|
9386
|
+
/**
|
|
9387
|
+
* Creates a callback function for emitting highest profit updates to highestProfitSubject.
|
|
9388
|
+
* Called by ClientStrategy when the highest profit for an open position is updated.
|
|
9389
|
+
* Emits HighestProfitContract event to all subscribers with the current price and timestamp.
|
|
9390
|
+
* Used for real-time profit tracking and management logic based on profit levels.
|
|
9391
|
+
*
|
|
9392
|
+
* @param self - Reference to StrategyConnectionService instance
|
|
9393
|
+
* @param strategyName - Name of the strategy
|
|
9394
|
+
* @param exchangeName - Name of the exchange
|
|
9395
|
+
* @param frameName - Name of the frame
|
|
9396
|
+
* @param isBacktest - Flag indicating if the operation is for backtesting
|
|
9397
|
+
* @returns Callback function for highest profit updates
|
|
9398
|
+
*/
|
|
9399
|
+
const CREATE_HIGHEST_PROFIT_FN = (self, strategyName, exchangeName, frameName, isBacktest) => functoolsKit.trycatch(async (signal, currentPrice, timestamp) => {
|
|
9400
|
+
await highestProfitSubject.next({
|
|
9401
|
+
symbol: signal.symbol,
|
|
9402
|
+
signal,
|
|
9403
|
+
currentPrice,
|
|
9404
|
+
timestamp,
|
|
9405
|
+
strategyName,
|
|
9406
|
+
exchangeName,
|
|
9407
|
+
frameName,
|
|
9408
|
+
backtest: isBacktest,
|
|
9409
|
+
});
|
|
9410
|
+
}, {
|
|
9411
|
+
fallback: (error) => {
|
|
9412
|
+
const message = "StrategyConnectionService CREATE_HIGHEST_PROFIT_FN thrown";
|
|
9413
|
+
const payload = {
|
|
9414
|
+
error: functoolsKit.errorData(error),
|
|
9415
|
+
message: functoolsKit.getErrorMessage(error),
|
|
9416
|
+
};
|
|
9417
|
+
bt.loggerService.warn(message, payload);
|
|
9418
|
+
console.warn(message, payload);
|
|
9419
|
+
errorEmitter.next(error);
|
|
9420
|
+
},
|
|
9421
|
+
defaultValue: null,
|
|
9422
|
+
});
|
|
9153
9423
|
/**
|
|
9154
9424
|
* Creates a callback function for emitting dispose events.
|
|
9155
9425
|
*
|
|
@@ -9220,7 +9490,7 @@ class StrategyConnectionService {
|
|
|
9220
9490
|
* @param backtest - Whether running in backtest mode
|
|
9221
9491
|
* @returns Configured ClientStrategy instance
|
|
9222
9492
|
*/
|
|
9223
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
9493
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$p(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9224
9494
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
9225
9495
|
return new ClientStrategy({
|
|
9226
9496
|
symbol,
|
|
@@ -9248,6 +9518,7 @@ class StrategyConnectionService {
|
|
|
9248
9518
|
onDispose: CREATE_COMMIT_DISPOSE_FN(this),
|
|
9249
9519
|
onCommit: CREATE_COMMIT_FN(),
|
|
9250
9520
|
onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
9521
|
+
onHighestProfit: CREATE_HIGHEST_PROFIT_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
9251
9522
|
});
|
|
9252
9523
|
});
|
|
9253
9524
|
/**
|
|
@@ -9321,14 +9592,14 @@ class StrategyConnectionService {
|
|
|
9321
9592
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9322
9593
|
* @returns Promise resolving to effective entry price or null
|
|
9323
9594
|
*/
|
|
9324
|
-
this.
|
|
9325
|
-
this.loggerService.log("strategyConnectionService
|
|
9595
|
+
this.getPositionEffectivePrice = async (backtest, symbol, context) => {
|
|
9596
|
+
this.loggerService.log("strategyConnectionService getPositionEffectivePrice", {
|
|
9326
9597
|
symbol,
|
|
9327
9598
|
context,
|
|
9328
9599
|
backtest,
|
|
9329
9600
|
});
|
|
9330
9601
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9331
|
-
return await strategy.
|
|
9602
|
+
return await strategy.getPositionEffectivePrice(symbol);
|
|
9332
9603
|
};
|
|
9333
9604
|
/**
|
|
9334
9605
|
* Returns the number of DCA entries made for the current pending signal.
|
|
@@ -9678,6 +9949,162 @@ class StrategyConnectionService {
|
|
|
9678
9949
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9679
9950
|
return await strategy.hasPendingSignal(symbol);
|
|
9680
9951
|
};
|
|
9952
|
+
/**
|
|
9953
|
+
* Returns the original estimated duration for the current pending signal.
|
|
9954
|
+
*
|
|
9955
|
+
* Delegates to ClientStrategy.getPositionEstimateMinutes().
|
|
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 estimated duration in minutes or null
|
|
9962
|
+
*/
|
|
9963
|
+
this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
|
|
9964
|
+
this.loggerService.log("strategyConnectionService getPositionEstimateMinutes", {
|
|
9965
|
+
symbol,
|
|
9966
|
+
context,
|
|
9967
|
+
});
|
|
9968
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9969
|
+
return await strategy.getPositionEstimateMinutes(symbol);
|
|
9970
|
+
};
|
|
9971
|
+
/**
|
|
9972
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
9973
|
+
*
|
|
9974
|
+
* Resolves current timestamp via timeMetaService and delegates to
|
|
9975
|
+
* ClientStrategy.getPositionCountdownMinutes().
|
|
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 remaining minutes (≥ 0) or null
|
|
9982
|
+
*/
|
|
9983
|
+
this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
|
|
9984
|
+
this.loggerService.log("strategyConnectionService getPositionCountdownMinutes", {
|
|
9985
|
+
symbol,
|
|
9986
|
+
context,
|
|
9987
|
+
});
|
|
9988
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9989
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9990
|
+
return await strategy.getPositionCountdownMinutes(symbol, timestamp);
|
|
9991
|
+
};
|
|
9992
|
+
/**
|
|
9993
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
9994
|
+
*
|
|
9995
|
+
* Delegates to ClientStrategy.getPositionHighestProfitPrice().
|
|
9996
|
+
* Returns null if no pending signal exists.
|
|
9997
|
+
*
|
|
9998
|
+
* @param backtest - Whether running in backtest mode
|
|
9999
|
+
* @param symbol - Trading pair symbol
|
|
10000
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10001
|
+
* @returns Promise resolving to price or null
|
|
10002
|
+
*/
|
|
10003
|
+
this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
|
|
10004
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitPrice", {
|
|
10005
|
+
symbol,
|
|
10006
|
+
context,
|
|
10007
|
+
});
|
|
10008
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10009
|
+
return await strategy.getPositionHighestProfitPrice(symbol);
|
|
10010
|
+
};
|
|
10011
|
+
/**
|
|
10012
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
10013
|
+
*
|
|
10014
|
+
* Delegates to ClientStrategy.getPositionHighestProfitTimestamp().
|
|
10015
|
+
* Returns null if no pending signal exists.
|
|
10016
|
+
*
|
|
10017
|
+
* @param backtest - Whether running in backtest mode
|
|
10018
|
+
* @param symbol - Trading pair symbol
|
|
10019
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10020
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
10021
|
+
*/
|
|
10022
|
+
this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
|
|
10023
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitTimestamp", {
|
|
10024
|
+
symbol,
|
|
10025
|
+
context,
|
|
10026
|
+
});
|
|
10027
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10028
|
+
return await strategy.getPositionHighestProfitTimestamp(symbol);
|
|
10029
|
+
};
|
|
10030
|
+
/**
|
|
10031
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
10032
|
+
*
|
|
10033
|
+
* Delegates to ClientStrategy.getPositionHighestPnlPercentage().
|
|
10034
|
+
* Returns null if no pending signal exists.
|
|
10035
|
+
*
|
|
10036
|
+
* @param backtest - Whether running in backtest mode
|
|
10037
|
+
* @param symbol - Trading pair symbol
|
|
10038
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10039
|
+
* @returns Promise resolving to PnL percentage or null
|
|
10040
|
+
*/
|
|
10041
|
+
this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
|
|
10042
|
+
this.loggerService.log("strategyConnectionService getPositionHighestPnlPercentage", {
|
|
10043
|
+
symbol,
|
|
10044
|
+
context,
|
|
10045
|
+
});
|
|
10046
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10047
|
+
return await strategy.getPositionHighestPnlPercentage(symbol);
|
|
10048
|
+
};
|
|
10049
|
+
/**
|
|
10050
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
10051
|
+
*
|
|
10052
|
+
* Delegates to ClientStrategy.getPositionHighestPnlCost().
|
|
10053
|
+
* Returns null if no pending signal exists.
|
|
10054
|
+
*
|
|
10055
|
+
* @param backtest - Whether running in backtest mode
|
|
10056
|
+
* @param symbol - Trading pair symbol
|
|
10057
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10058
|
+
* @returns Promise resolving to PnL cost or null
|
|
10059
|
+
*/
|
|
10060
|
+
this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
|
|
10061
|
+
this.loggerService.log("strategyConnectionService getPositionHighestPnlCost", {
|
|
10062
|
+
symbol,
|
|
10063
|
+
context,
|
|
10064
|
+
});
|
|
10065
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10066
|
+
return await strategy.getPositionHighestPnlCost(symbol);
|
|
10067
|
+
};
|
|
10068
|
+
/**
|
|
10069
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
10070
|
+
*
|
|
10071
|
+
* Delegates to ClientStrategy.getPositionHighestProfitBreakeven().
|
|
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 true if breakeven was reachable at peak, false otherwise, or null
|
|
10078
|
+
*/
|
|
10079
|
+
this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
|
|
10080
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitBreakeven", {
|
|
10081
|
+
symbol,
|
|
10082
|
+
context,
|
|
10083
|
+
});
|
|
10084
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10085
|
+
return await strategy.getPositionHighestProfitBreakeven(symbol);
|
|
10086
|
+
};
|
|
10087
|
+
/**
|
|
10088
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
10089
|
+
*
|
|
10090
|
+
* Resolves current timestamp via timeMetaService and delegates to
|
|
10091
|
+
* ClientStrategy.getPositionDrawdownMinutes().
|
|
10092
|
+
* Returns null if no pending signal exists.
|
|
10093
|
+
*
|
|
10094
|
+
* @param backtest - Whether running in backtest mode
|
|
10095
|
+
* @param symbol - Trading pair symbol
|
|
10096
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10097
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
10098
|
+
*/
|
|
10099
|
+
this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
|
|
10100
|
+
this.loggerService.log("strategyConnectionService getPositionDrawdownMinutes", {
|
|
10101
|
+
symbol,
|
|
10102
|
+
context,
|
|
10103
|
+
});
|
|
10104
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10105
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
10106
|
+
return await strategy.getPositionDrawdownMinutes(symbol, timestamp);
|
|
10107
|
+
};
|
|
9681
10108
|
/**
|
|
9682
10109
|
* Disposes the ClientStrategy instance for the given context.
|
|
9683
10110
|
*
|
|
@@ -9716,7 +10143,7 @@ class StrategyConnectionService {
|
|
|
9716
10143
|
}
|
|
9717
10144
|
return;
|
|
9718
10145
|
}
|
|
9719
|
-
const key = CREATE_KEY_FN$
|
|
10146
|
+
const key = CREATE_KEY_FN$p(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
9720
10147
|
if (!this.getStrategy.has(key)) {
|
|
9721
10148
|
return;
|
|
9722
10149
|
}
|
|
@@ -10889,7 +11316,7 @@ class ClientRisk {
|
|
|
10889
11316
|
* @param backtest - Whether running in backtest mode
|
|
10890
11317
|
* @returns Unique string key for memoization
|
|
10891
11318
|
*/
|
|
10892
|
-
const CREATE_KEY_FN$
|
|
11319
|
+
const CREATE_KEY_FN$o = (riskName, exchangeName, frameName, backtest) => {
|
|
10893
11320
|
const parts = [riskName, exchangeName];
|
|
10894
11321
|
if (frameName)
|
|
10895
11322
|
parts.push(frameName);
|
|
@@ -10988,7 +11415,7 @@ class RiskConnectionService {
|
|
|
10988
11415
|
* @param backtest - True if backtest mode, false if live mode
|
|
10989
11416
|
* @returns Configured ClientRisk instance
|
|
10990
11417
|
*/
|
|
10991
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
11418
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
10992
11419
|
const schema = this.riskSchemaService.get(riskName);
|
|
10993
11420
|
return new ClientRisk({
|
|
10994
11421
|
...schema,
|
|
@@ -11056,7 +11483,7 @@ class RiskConnectionService {
|
|
|
11056
11483
|
payload,
|
|
11057
11484
|
});
|
|
11058
11485
|
if (payload) {
|
|
11059
|
-
const key = CREATE_KEY_FN$
|
|
11486
|
+
const key = CREATE_KEY_FN$o(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
11060
11487
|
this.getRisk.clear(key);
|
|
11061
11488
|
}
|
|
11062
11489
|
else {
|
|
@@ -12523,7 +12950,7 @@ class ClientAction {
|
|
|
12523
12950
|
* @param backtest - Whether running in backtest mode
|
|
12524
12951
|
* @returns Unique string key for memoization
|
|
12525
12952
|
*/
|
|
12526
|
-
const CREATE_KEY_FN$
|
|
12953
|
+
const CREATE_KEY_FN$n = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12527
12954
|
const parts = [actionName, strategyName, exchangeName];
|
|
12528
12955
|
if (frameName)
|
|
12529
12956
|
parts.push(frameName);
|
|
@@ -12574,7 +13001,7 @@ class ActionConnectionService {
|
|
|
12574
13001
|
* @param backtest - True if backtest mode, false if live mode
|
|
12575
13002
|
* @returns Configured ClientAction instance
|
|
12576
13003
|
*/
|
|
12577
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
13004
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12578
13005
|
const schema = this.actionSchemaService.get(actionName);
|
|
12579
13006
|
return new ClientAction({
|
|
12580
13007
|
...schema,
|
|
@@ -12784,7 +13211,7 @@ class ActionConnectionService {
|
|
|
12784
13211
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
12785
13212
|
return;
|
|
12786
13213
|
}
|
|
12787
|
-
const key = CREATE_KEY_FN$
|
|
13214
|
+
const key = CREATE_KEY_FN$n(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12788
13215
|
if (!this.getAction.has(key)) {
|
|
12789
13216
|
return;
|
|
12790
13217
|
}
|
|
@@ -12802,7 +13229,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
12802
13229
|
* @param exchangeName - Exchange name
|
|
12803
13230
|
* @returns Unique string key for memoization
|
|
12804
13231
|
*/
|
|
12805
|
-
const CREATE_KEY_FN$
|
|
13232
|
+
const CREATE_KEY_FN$m = (exchangeName) => {
|
|
12806
13233
|
return exchangeName;
|
|
12807
13234
|
};
|
|
12808
13235
|
/**
|
|
@@ -12826,7 +13253,7 @@ class ExchangeCoreService {
|
|
|
12826
13253
|
* @param exchangeName - Name of the exchange to validate
|
|
12827
13254
|
* @returns Promise that resolves when validation is complete
|
|
12828
13255
|
*/
|
|
12829
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
13256
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$m(exchangeName), async (exchangeName) => {
|
|
12830
13257
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
12831
13258
|
exchangeName,
|
|
12832
13259
|
});
|
|
@@ -13078,7 +13505,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
13078
13505
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13079
13506
|
* @returns Unique string key for memoization
|
|
13080
13507
|
*/
|
|
13081
|
-
const CREATE_KEY_FN$
|
|
13508
|
+
const CREATE_KEY_FN$l = (context) => {
|
|
13082
13509
|
const parts = [context.strategyName, context.exchangeName];
|
|
13083
13510
|
if (context.frameName)
|
|
13084
13511
|
parts.push(context.frameName);
|
|
@@ -13110,7 +13537,7 @@ class StrategyCoreService {
|
|
|
13110
13537
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13111
13538
|
* @returns Promise that resolves when validation is complete
|
|
13112
13539
|
*/
|
|
13113
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
13540
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$l(context), async (context) => {
|
|
13114
13541
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
13115
13542
|
context,
|
|
13116
13543
|
});
|
|
@@ -13174,13 +13601,13 @@ class StrategyCoreService {
|
|
|
13174
13601
|
await this.validate(context);
|
|
13175
13602
|
return await this.strategyConnectionService.getTotalCostClosed(backtest, symbol, context);
|
|
13176
13603
|
};
|
|
13177
|
-
this.
|
|
13178
|
-
this.loggerService.log("strategyCoreService
|
|
13604
|
+
this.getPositionEffectivePrice = async (backtest, symbol, context) => {
|
|
13605
|
+
this.loggerService.log("strategyCoreService getPositionEffectivePrice", {
|
|
13179
13606
|
symbol,
|
|
13180
13607
|
context,
|
|
13181
13608
|
});
|
|
13182
13609
|
await this.validate(context);
|
|
13183
|
-
return await this.strategyConnectionService.
|
|
13610
|
+
return await this.strategyConnectionService.getPositionEffectivePrice(backtest, symbol, context);
|
|
13184
13611
|
};
|
|
13185
13612
|
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
13186
13613
|
this.loggerService.log("strategyCoreService getPositionInvestedCount", {
|
|
@@ -13873,6 +14300,143 @@ class StrategyCoreService {
|
|
|
13873
14300
|
await this.validate(context);
|
|
13874
14301
|
return await this.strategyConnectionService.hasPendingSignal(backtest, symbol, context);
|
|
13875
14302
|
};
|
|
14303
|
+
/**
|
|
14304
|
+
* Returns the original estimated duration for the current pending signal.
|
|
14305
|
+
*
|
|
14306
|
+
* Validates strategy existence and delegates to connection service.
|
|
14307
|
+
* Returns null if no pending signal exists.
|
|
14308
|
+
*
|
|
14309
|
+
* @param backtest - Whether running in backtest mode
|
|
14310
|
+
* @param symbol - Trading pair symbol
|
|
14311
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14312
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
14313
|
+
*/
|
|
14314
|
+
this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
|
|
14315
|
+
this.loggerService.log("strategyCoreService getPositionEstimateMinutes", {
|
|
14316
|
+
symbol,
|
|
14317
|
+
context,
|
|
14318
|
+
});
|
|
14319
|
+
await this.validate(context);
|
|
14320
|
+
return await this.strategyConnectionService.getPositionEstimateMinutes(backtest, symbol, context);
|
|
14321
|
+
};
|
|
14322
|
+
/**
|
|
14323
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
14324
|
+
*
|
|
14325
|
+
* Validates strategy existence and delegates to connection service.
|
|
14326
|
+
* Returns null if no pending signal exists.
|
|
14327
|
+
*
|
|
14328
|
+
* @param backtest - Whether running in backtest mode
|
|
14329
|
+
* @param symbol - Trading pair symbol
|
|
14330
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14331
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
14332
|
+
*/
|
|
14333
|
+
this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
|
|
14334
|
+
this.loggerService.log("strategyCoreService getPositionCountdownMinutes", {
|
|
14335
|
+
symbol,
|
|
14336
|
+
context,
|
|
14337
|
+
});
|
|
14338
|
+
await this.validate(context);
|
|
14339
|
+
return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
|
|
14340
|
+
};
|
|
14341
|
+
/**
|
|
14342
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
14343
|
+
*
|
|
14344
|
+
* @param backtest - Whether running in backtest mode
|
|
14345
|
+
* @param symbol - Trading pair symbol
|
|
14346
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14347
|
+
* @returns Promise resolving to price or null
|
|
14348
|
+
*/
|
|
14349
|
+
this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
|
|
14350
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitPrice", {
|
|
14351
|
+
symbol,
|
|
14352
|
+
context,
|
|
14353
|
+
});
|
|
14354
|
+
await this.validate(context);
|
|
14355
|
+
return await this.strategyConnectionService.getPositionHighestProfitPrice(backtest, symbol, context);
|
|
14356
|
+
};
|
|
14357
|
+
/**
|
|
14358
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
14359
|
+
*
|
|
14360
|
+
* @param backtest - Whether running in backtest mode
|
|
14361
|
+
* @param symbol - Trading pair symbol
|
|
14362
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14363
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
14364
|
+
*/
|
|
14365
|
+
this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
|
|
14366
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitTimestamp", {
|
|
14367
|
+
symbol,
|
|
14368
|
+
context,
|
|
14369
|
+
});
|
|
14370
|
+
await this.validate(context);
|
|
14371
|
+
return await this.strategyConnectionService.getPositionHighestProfitTimestamp(backtest, symbol, context);
|
|
14372
|
+
};
|
|
14373
|
+
/**
|
|
14374
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
14375
|
+
*
|
|
14376
|
+
* @param backtest - Whether running in backtest mode
|
|
14377
|
+
* @param symbol - Trading pair symbol
|
|
14378
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14379
|
+
* @returns Promise resolving to PnL percentage or null
|
|
14380
|
+
*/
|
|
14381
|
+
this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
|
|
14382
|
+
this.loggerService.log("strategyCoreService getPositionHighestPnlPercentage", {
|
|
14383
|
+
symbol,
|
|
14384
|
+
context,
|
|
14385
|
+
});
|
|
14386
|
+
await this.validate(context);
|
|
14387
|
+
return await this.strategyConnectionService.getPositionHighestPnlPercentage(backtest, symbol, context);
|
|
14388
|
+
};
|
|
14389
|
+
/**
|
|
14390
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
14391
|
+
*
|
|
14392
|
+
* @param backtest - Whether running in backtest mode
|
|
14393
|
+
* @param symbol - Trading pair symbol
|
|
14394
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14395
|
+
* @returns Promise resolving to PnL cost or null
|
|
14396
|
+
*/
|
|
14397
|
+
this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
|
|
14398
|
+
this.loggerService.log("strategyCoreService getPositionHighestPnlCost", {
|
|
14399
|
+
symbol,
|
|
14400
|
+
context,
|
|
14401
|
+
});
|
|
14402
|
+
await this.validate(context);
|
|
14403
|
+
return await this.strategyConnectionService.getPositionHighestPnlCost(backtest, symbol, context);
|
|
14404
|
+
};
|
|
14405
|
+
/**
|
|
14406
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
14407
|
+
*
|
|
14408
|
+
* @param backtest - Whether running in backtest mode
|
|
14409
|
+
* @param symbol - Trading pair symbol
|
|
14410
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14411
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
14412
|
+
*/
|
|
14413
|
+
this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
|
|
14414
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitBreakeven", {
|
|
14415
|
+
symbol,
|
|
14416
|
+
context,
|
|
14417
|
+
});
|
|
14418
|
+
await this.validate(context);
|
|
14419
|
+
return await this.strategyConnectionService.getPositionHighestProfitBreakeven(backtest, symbol, context);
|
|
14420
|
+
};
|
|
14421
|
+
/**
|
|
14422
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
14423
|
+
*
|
|
14424
|
+
* Validates strategy existence and delegates to connection service.
|
|
14425
|
+
* Returns null if no pending signal exists.
|
|
14426
|
+
*
|
|
14427
|
+
* @param backtest - Whether running in backtest mode
|
|
14428
|
+
* @param symbol - Trading pair symbol
|
|
14429
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14430
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
14431
|
+
*/
|
|
14432
|
+
this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
|
|
14433
|
+
this.loggerService.log("strategyCoreService getPositionDrawdownMinutes", {
|
|
14434
|
+
symbol,
|
|
14435
|
+
context,
|
|
14436
|
+
});
|
|
14437
|
+
await this.validate(context);
|
|
14438
|
+
return await this.strategyConnectionService.getPositionDrawdownMinutes(backtest, symbol, context);
|
|
14439
|
+
};
|
|
13876
14440
|
}
|
|
13877
14441
|
}
|
|
13878
14442
|
|
|
@@ -13945,7 +14509,7 @@ class SizingGlobalService {
|
|
|
13945
14509
|
* @param context - Context with riskName, exchangeName, frameName
|
|
13946
14510
|
* @returns Unique string key for memoization
|
|
13947
14511
|
*/
|
|
13948
|
-
const CREATE_KEY_FN$
|
|
14512
|
+
const CREATE_KEY_FN$k = (context) => {
|
|
13949
14513
|
const parts = [context.riskName, context.exchangeName];
|
|
13950
14514
|
if (context.frameName)
|
|
13951
14515
|
parts.push(context.frameName);
|
|
@@ -13971,7 +14535,7 @@ class RiskGlobalService {
|
|
|
13971
14535
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
13972
14536
|
* @returns Promise that resolves when validation is complete
|
|
13973
14537
|
*/
|
|
13974
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
14538
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
|
|
13975
14539
|
this.loggerService.log("riskGlobalService validate", {
|
|
13976
14540
|
context,
|
|
13977
14541
|
});
|
|
@@ -14049,7 +14613,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
14049
14613
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14050
14614
|
* @returns Unique string key for memoization
|
|
14051
14615
|
*/
|
|
14052
|
-
const CREATE_KEY_FN$
|
|
14616
|
+
const CREATE_KEY_FN$j = (context) => {
|
|
14053
14617
|
const parts = [context.strategyName, context.exchangeName];
|
|
14054
14618
|
if (context.frameName)
|
|
14055
14619
|
parts.push(context.frameName);
|
|
@@ -14093,7 +14657,7 @@ class ActionCoreService {
|
|
|
14093
14657
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
14094
14658
|
* @returns Promise that resolves when all validations complete
|
|
14095
14659
|
*/
|
|
14096
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
14660
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
|
|
14097
14661
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
14098
14662
|
context,
|
|
14099
14663
|
});
|
|
@@ -17904,6 +18468,78 @@ const sync_columns = [
|
|
|
17904
18468
|
},
|
|
17905
18469
|
];
|
|
17906
18470
|
|
|
18471
|
+
/**
|
|
18472
|
+
* Column configuration for highest profit markdown reports.
|
|
18473
|
+
*
|
|
18474
|
+
* Defines the table structure for displaying highest-profit-record events.
|
|
18475
|
+
*
|
|
18476
|
+
* @see HighestProfitMarkdownService
|
|
18477
|
+
* @see ColumnModel
|
|
18478
|
+
* @see HighestProfitEvent
|
|
18479
|
+
*/
|
|
18480
|
+
const highest_profit_columns = [
|
|
18481
|
+
{
|
|
18482
|
+
key: "symbol",
|
|
18483
|
+
label: "Symbol",
|
|
18484
|
+
format: (data) => data.symbol,
|
|
18485
|
+
isVisible: () => true,
|
|
18486
|
+
},
|
|
18487
|
+
{
|
|
18488
|
+
key: "strategyName",
|
|
18489
|
+
label: "Strategy",
|
|
18490
|
+
format: (data) => data.strategyName,
|
|
18491
|
+
isVisible: () => true,
|
|
18492
|
+
},
|
|
18493
|
+
{
|
|
18494
|
+
key: "signalId",
|
|
18495
|
+
label: "Signal ID",
|
|
18496
|
+
format: (data) => data.signalId,
|
|
18497
|
+
isVisible: () => true,
|
|
18498
|
+
},
|
|
18499
|
+
{
|
|
18500
|
+
key: "position",
|
|
18501
|
+
label: "Position",
|
|
18502
|
+
format: (data) => data.position.toUpperCase(),
|
|
18503
|
+
isVisible: () => true,
|
|
18504
|
+
},
|
|
18505
|
+
{
|
|
18506
|
+
key: "currentPrice",
|
|
18507
|
+
label: "Record Price",
|
|
18508
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
18509
|
+
isVisible: () => true,
|
|
18510
|
+
},
|
|
18511
|
+
{
|
|
18512
|
+
key: "priceOpen",
|
|
18513
|
+
label: "Entry Price",
|
|
18514
|
+
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
18515
|
+
isVisible: () => true,
|
|
18516
|
+
},
|
|
18517
|
+
{
|
|
18518
|
+
key: "priceTakeProfit",
|
|
18519
|
+
label: "Take Profit",
|
|
18520
|
+
format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
|
|
18521
|
+
isVisible: () => true,
|
|
18522
|
+
},
|
|
18523
|
+
{
|
|
18524
|
+
key: "priceStopLoss",
|
|
18525
|
+
label: "Stop Loss",
|
|
18526
|
+
format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
|
|
18527
|
+
isVisible: () => true,
|
|
18528
|
+
},
|
|
18529
|
+
{
|
|
18530
|
+
key: "timestamp",
|
|
18531
|
+
label: "Timestamp",
|
|
18532
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
18533
|
+
isVisible: () => true,
|
|
18534
|
+
},
|
|
18535
|
+
{
|
|
18536
|
+
key: "mode",
|
|
18537
|
+
label: "Mode",
|
|
18538
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
18539
|
+
isVisible: () => true,
|
|
18540
|
+
},
|
|
18541
|
+
];
|
|
18542
|
+
|
|
17907
18543
|
/**
|
|
17908
18544
|
* Column configuration for walker strategy comparison table in markdown reports.
|
|
17909
18545
|
*
|
|
@@ -18121,6 +18757,8 @@ const COLUMN_CONFIG = {
|
|
|
18121
18757
|
strategy_columns,
|
|
18122
18758
|
/** Columns for signal sync lifecycle events (signal-open, signal-close) */
|
|
18123
18759
|
sync_columns,
|
|
18760
|
+
/** Columns for highest profit milestone tracking events */
|
|
18761
|
+
highest_profit_columns,
|
|
18124
18762
|
/** Walker: PnL summary columns */
|
|
18125
18763
|
walker_pnl_columns,
|
|
18126
18764
|
/** Walker: strategy-level summary columns */
|
|
@@ -18161,6 +18799,7 @@ const WILDCARD_TARGET$1 = {
|
|
|
18161
18799
|
schedule: true,
|
|
18162
18800
|
walker: true,
|
|
18163
18801
|
sync: true,
|
|
18802
|
+
highest_profit: true,
|
|
18164
18803
|
};
|
|
18165
18804
|
/**
|
|
18166
18805
|
* JSONL-based markdown adapter with append-only writes.
|
|
@@ -18388,7 +19027,7 @@ class MarkdownUtils {
|
|
|
18388
19027
|
*
|
|
18389
19028
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
18390
19029
|
*/
|
|
18391
|
-
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) => {
|
|
19030
|
+
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) => {
|
|
18392
19031
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
|
|
18393
19032
|
backtest: bt$1,
|
|
18394
19033
|
breakeven,
|
|
@@ -18401,6 +19040,7 @@ class MarkdownUtils {
|
|
|
18401
19040
|
schedule,
|
|
18402
19041
|
walker,
|
|
18403
19042
|
sync,
|
|
19043
|
+
highest_profit,
|
|
18404
19044
|
});
|
|
18405
19045
|
const unList = [];
|
|
18406
19046
|
if (bt$1) {
|
|
@@ -18436,6 +19076,9 @@ class MarkdownUtils {
|
|
|
18436
19076
|
if (sync) {
|
|
18437
19077
|
unList.push(bt.syncMarkdownService.subscribe());
|
|
18438
19078
|
}
|
|
19079
|
+
if (highest_profit) {
|
|
19080
|
+
unList.push(bt.highestProfitMarkdownService.subscribe());
|
|
19081
|
+
}
|
|
18439
19082
|
return functoolsKit.compose(...unList.map((un) => () => void un()));
|
|
18440
19083
|
};
|
|
18441
19084
|
/**
|
|
@@ -18475,7 +19118,7 @@ class MarkdownUtils {
|
|
|
18475
19118
|
* Markdown.disable();
|
|
18476
19119
|
* ```
|
|
18477
19120
|
*/
|
|
18478
|
-
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) => {
|
|
19121
|
+
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) => {
|
|
18479
19122
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
|
|
18480
19123
|
backtest: bt$1,
|
|
18481
19124
|
breakeven,
|
|
@@ -18488,6 +19131,7 @@ class MarkdownUtils {
|
|
|
18488
19131
|
schedule,
|
|
18489
19132
|
walker,
|
|
18490
19133
|
sync,
|
|
19134
|
+
highest_profit,
|
|
18491
19135
|
});
|
|
18492
19136
|
if (bt$1) {
|
|
18493
19137
|
bt.backtestMarkdownService.unsubscribe();
|
|
@@ -18522,6 +19166,9 @@ class MarkdownUtils {
|
|
|
18522
19166
|
if (sync) {
|
|
18523
19167
|
bt.syncMarkdownService.unsubscribe();
|
|
18524
19168
|
}
|
|
19169
|
+
if (highest_profit) {
|
|
19170
|
+
bt.highestProfitMarkdownService.unsubscribe();
|
|
19171
|
+
}
|
|
18525
19172
|
};
|
|
18526
19173
|
}
|
|
18527
19174
|
}
|
|
@@ -18628,7 +19275,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
18628
19275
|
* @param backtest - Whether running in backtest mode
|
|
18629
19276
|
* @returns Unique string key for memoization
|
|
18630
19277
|
*/
|
|
18631
|
-
const CREATE_KEY_FN$
|
|
19278
|
+
const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
18632
19279
|
const parts = [symbol, strategyName, exchangeName];
|
|
18633
19280
|
if (frameName)
|
|
18634
19281
|
parts.push(frameName);
|
|
@@ -18645,7 +19292,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
18645
19292
|
* @param timestamp - Unix timestamp in milliseconds
|
|
18646
19293
|
* @returns Filename string
|
|
18647
19294
|
*/
|
|
18648
|
-
const CREATE_FILE_NAME_FN$
|
|
19295
|
+
const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
18649
19296
|
const parts = [symbol, strategyName, exchangeName];
|
|
18650
19297
|
if (frameName) {
|
|
18651
19298
|
parts.push(frameName);
|
|
@@ -18674,12 +19321,12 @@ function isUnsafe$3(value) {
|
|
|
18674
19321
|
return false;
|
|
18675
19322
|
}
|
|
18676
19323
|
/** Maximum number of signals to store in backtest reports */
|
|
18677
|
-
const MAX_EVENTS$
|
|
19324
|
+
const MAX_EVENTS$a = 250;
|
|
18678
19325
|
/**
|
|
18679
19326
|
* Storage class for accumulating closed signals per strategy.
|
|
18680
19327
|
* Maintains a list of all closed signals and provides methods to generate reports.
|
|
18681
19328
|
*/
|
|
18682
|
-
let ReportStorage$
|
|
19329
|
+
let ReportStorage$9 = class ReportStorage {
|
|
18683
19330
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
18684
19331
|
this.symbol = symbol;
|
|
18685
19332
|
this.strategyName = strategyName;
|
|
@@ -18696,7 +19343,7 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
18696
19343
|
addSignal(data) {
|
|
18697
19344
|
this._signalList.unshift(data);
|
|
18698
19345
|
// Trim queue if exceeded MAX_EVENTS
|
|
18699
|
-
if (this._signalList.length > MAX_EVENTS$
|
|
19346
|
+
if (this._signalList.length > MAX_EVENTS$a) {
|
|
18700
19347
|
this._signalList.pop();
|
|
18701
19348
|
}
|
|
18702
19349
|
}
|
|
@@ -18820,7 +19467,7 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
18820
19467
|
async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
18821
19468
|
const markdown = await this.getReport(strategyName, columns);
|
|
18822
19469
|
const timestamp = getContextTimestamp();
|
|
18823
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
19470
|
+
const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
18824
19471
|
await Markdown.writeData("backtest", markdown, {
|
|
18825
19472
|
path,
|
|
18826
19473
|
file: filename,
|
|
@@ -18867,7 +19514,7 @@ class BacktestMarkdownService {
|
|
|
18867
19514
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
18868
19515
|
* Each combination gets its own isolated storage instance.
|
|
18869
19516
|
*/
|
|
18870
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
19517
|
+
this.getStorage = functoolsKit.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));
|
|
18871
19518
|
/**
|
|
18872
19519
|
* Processes tick events and accumulates closed signals.
|
|
18873
19520
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -19024,7 +19671,7 @@ class BacktestMarkdownService {
|
|
|
19024
19671
|
payload,
|
|
19025
19672
|
});
|
|
19026
19673
|
if (payload) {
|
|
19027
|
-
const key = CREATE_KEY_FN$
|
|
19674
|
+
const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19028
19675
|
this.getStorage.clear(key);
|
|
19029
19676
|
}
|
|
19030
19677
|
else {
|
|
@@ -19086,7 +19733,7 @@ class BacktestMarkdownService {
|
|
|
19086
19733
|
* @param backtest - Whether running in backtest mode
|
|
19087
19734
|
* @returns Unique string key for memoization
|
|
19088
19735
|
*/
|
|
19089
|
-
const CREATE_KEY_FN$
|
|
19736
|
+
const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19090
19737
|
const parts = [symbol, strategyName, exchangeName];
|
|
19091
19738
|
if (frameName)
|
|
19092
19739
|
parts.push(frameName);
|
|
@@ -19103,7 +19750,7 @@ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19103
19750
|
* @param timestamp - Unix timestamp in milliseconds
|
|
19104
19751
|
* @returns Filename string
|
|
19105
19752
|
*/
|
|
19106
|
-
const CREATE_FILE_NAME_FN$
|
|
19753
|
+
const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19107
19754
|
const parts = [symbol, strategyName, exchangeName];
|
|
19108
19755
|
if (frameName) {
|
|
19109
19756
|
parts.push(frameName);
|
|
@@ -19132,12 +19779,12 @@ function isUnsafe$2(value) {
|
|
|
19132
19779
|
return false;
|
|
19133
19780
|
}
|
|
19134
19781
|
/** Maximum number of events to store in live trading reports */
|
|
19135
|
-
const MAX_EVENTS$
|
|
19782
|
+
const MAX_EVENTS$9 = 250;
|
|
19136
19783
|
/**
|
|
19137
19784
|
* Storage class for accumulating all tick events per strategy.
|
|
19138
19785
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
19139
19786
|
*/
|
|
19140
|
-
let ReportStorage$
|
|
19787
|
+
let ReportStorage$8 = class ReportStorage {
|
|
19141
19788
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19142
19789
|
this.symbol = symbol;
|
|
19143
19790
|
this.strategyName = strategyName;
|
|
@@ -19169,7 +19816,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19169
19816
|
}
|
|
19170
19817
|
{
|
|
19171
19818
|
this._eventList.unshift(newEvent);
|
|
19172
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19819
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19173
19820
|
this._eventList.pop();
|
|
19174
19821
|
}
|
|
19175
19822
|
}
|
|
@@ -19199,7 +19846,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19199
19846
|
scheduledAt: data.signal.scheduledAt,
|
|
19200
19847
|
});
|
|
19201
19848
|
// Trim queue if exceeded MAX_EVENTS
|
|
19202
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19849
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19203
19850
|
this._eventList.pop();
|
|
19204
19851
|
}
|
|
19205
19852
|
}
|
|
@@ -19243,7 +19890,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19243
19890
|
// If no previous active event found, add new event
|
|
19244
19891
|
this._eventList.unshift(newEvent);
|
|
19245
19892
|
// Trim queue if exceeded MAX_EVENTS
|
|
19246
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19893
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19247
19894
|
this._eventList.pop();
|
|
19248
19895
|
}
|
|
19249
19896
|
}
|
|
@@ -19280,7 +19927,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19280
19927
|
};
|
|
19281
19928
|
this._eventList.unshift(newEvent);
|
|
19282
19929
|
// Trim queue if exceeded MAX_EVENTS
|
|
19283
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19930
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19284
19931
|
this._eventList.pop();
|
|
19285
19932
|
}
|
|
19286
19933
|
}
|
|
@@ -19308,7 +19955,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19308
19955
|
scheduledAt: data.signal.scheduledAt,
|
|
19309
19956
|
});
|
|
19310
19957
|
// Trim queue if exceeded MAX_EVENTS
|
|
19311
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
19958
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19312
19959
|
this._eventList.pop();
|
|
19313
19960
|
}
|
|
19314
19961
|
}
|
|
@@ -19351,7 +19998,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19351
19998
|
// If no previous waiting event found, add new event
|
|
19352
19999
|
this._eventList.unshift(newEvent);
|
|
19353
20000
|
// Trim queue if exceeded MAX_EVENTS
|
|
19354
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20001
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19355
20002
|
this._eventList.pop();
|
|
19356
20003
|
}
|
|
19357
20004
|
}
|
|
@@ -19380,7 +20027,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19380
20027
|
scheduledAt: data.signal.scheduledAt,
|
|
19381
20028
|
});
|
|
19382
20029
|
// Trim queue if exceeded MAX_EVENTS
|
|
19383
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20030
|
+
if (this._eventList.length > MAX_EVENTS$9) {
|
|
19384
20031
|
this._eventList.pop();
|
|
19385
20032
|
}
|
|
19386
20033
|
}
|
|
@@ -19519,7 +20166,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
19519
20166
|
async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
19520
20167
|
const markdown = await this.getReport(strategyName, columns);
|
|
19521
20168
|
const timestamp = getContextTimestamp();
|
|
19522
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20169
|
+
const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
19523
20170
|
await Markdown.writeData("live", markdown, {
|
|
19524
20171
|
path,
|
|
19525
20172
|
signalId: "",
|
|
@@ -19569,7 +20216,7 @@ class LiveMarkdownService {
|
|
|
19569
20216
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19570
20217
|
* Each combination gets its own isolated storage instance.
|
|
19571
20218
|
*/
|
|
19572
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20219
|
+
this.getStorage = functoolsKit.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));
|
|
19573
20220
|
/**
|
|
19574
20221
|
* Subscribes to live signal emitter to receive tick events.
|
|
19575
20222
|
* Protected against multiple subscriptions.
|
|
@@ -19787,7 +20434,7 @@ class LiveMarkdownService {
|
|
|
19787
20434
|
payload,
|
|
19788
20435
|
});
|
|
19789
20436
|
if (payload) {
|
|
19790
|
-
const key = CREATE_KEY_FN$
|
|
20437
|
+
const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19791
20438
|
this.getStorage.clear(key);
|
|
19792
20439
|
}
|
|
19793
20440
|
else {
|
|
@@ -19807,7 +20454,7 @@ class LiveMarkdownService {
|
|
|
19807
20454
|
* @param backtest - Whether running in backtest mode
|
|
19808
20455
|
* @returns Unique string key for memoization
|
|
19809
20456
|
*/
|
|
19810
|
-
const CREATE_KEY_FN$
|
|
20457
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19811
20458
|
const parts = [symbol, strategyName, exchangeName];
|
|
19812
20459
|
if (frameName)
|
|
19813
20460
|
parts.push(frameName);
|
|
@@ -19824,7 +20471,7 @@ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
19824
20471
|
* @param timestamp - Unix timestamp in milliseconds
|
|
19825
20472
|
* @returns Filename string
|
|
19826
20473
|
*/
|
|
19827
|
-
const CREATE_FILE_NAME_FN$
|
|
20474
|
+
const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
19828
20475
|
const parts = [symbol, strategyName, exchangeName];
|
|
19829
20476
|
if (frameName) {
|
|
19830
20477
|
parts.push(frameName);
|
|
@@ -19835,12 +20482,12 @@ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
19835
20482
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
19836
20483
|
};
|
|
19837
20484
|
/** Maximum number of events to store in schedule reports */
|
|
19838
|
-
const MAX_EVENTS$
|
|
20485
|
+
const MAX_EVENTS$8 = 250;
|
|
19839
20486
|
/**
|
|
19840
20487
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
19841
20488
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
19842
20489
|
*/
|
|
19843
|
-
let ReportStorage$
|
|
20490
|
+
let ReportStorage$7 = class ReportStorage {
|
|
19844
20491
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
19845
20492
|
this.symbol = symbol;
|
|
19846
20493
|
this.strategyName = strategyName;
|
|
@@ -19876,7 +20523,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19876
20523
|
scheduledAt: data.signal.scheduledAt,
|
|
19877
20524
|
});
|
|
19878
20525
|
// Trim queue if exceeded MAX_EVENTS
|
|
19879
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20526
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19880
20527
|
this._eventList.pop();
|
|
19881
20528
|
}
|
|
19882
20529
|
}
|
|
@@ -19912,7 +20559,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19912
20559
|
};
|
|
19913
20560
|
this._eventList.unshift(newEvent);
|
|
19914
20561
|
// Trim queue if exceeded MAX_EVENTS
|
|
19915
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20562
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19916
20563
|
this._eventList.pop();
|
|
19917
20564
|
}
|
|
19918
20565
|
}
|
|
@@ -19950,7 +20597,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
19950
20597
|
};
|
|
19951
20598
|
this._eventList.unshift(newEvent);
|
|
19952
20599
|
// Trim queue if exceeded MAX_EVENTS
|
|
19953
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
20600
|
+
if (this._eventList.length > MAX_EVENTS$8) {
|
|
19954
20601
|
this._eventList.pop();
|
|
19955
20602
|
}
|
|
19956
20603
|
}
|
|
@@ -20057,7 +20704,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
20057
20704
|
async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
20058
20705
|
const markdown = await this.getReport(strategyName, columns);
|
|
20059
20706
|
const timestamp = getContextTimestamp();
|
|
20060
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
20707
|
+
const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20061
20708
|
await Markdown.writeData("schedule", markdown, {
|
|
20062
20709
|
path,
|
|
20063
20710
|
file: filename,
|
|
@@ -20098,7 +20745,7 @@ class ScheduleMarkdownService {
|
|
|
20098
20745
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20099
20746
|
* Each combination gets its own isolated storage instance.
|
|
20100
20747
|
*/
|
|
20101
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20748
|
+
this.getStorage = functoolsKit.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));
|
|
20102
20749
|
/**
|
|
20103
20750
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
20104
20751
|
* Protected against multiple subscriptions.
|
|
@@ -20301,7 +20948,7 @@ class ScheduleMarkdownService {
|
|
|
20301
20948
|
payload,
|
|
20302
20949
|
});
|
|
20303
20950
|
if (payload) {
|
|
20304
|
-
const key = CREATE_KEY_FN$
|
|
20951
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20305
20952
|
this.getStorage.clear(key);
|
|
20306
20953
|
}
|
|
20307
20954
|
else {
|
|
@@ -20321,7 +20968,7 @@ class ScheduleMarkdownService {
|
|
|
20321
20968
|
* @param backtest - Whether running in backtest mode
|
|
20322
20969
|
* @returns Unique string key for memoization
|
|
20323
20970
|
*/
|
|
20324
|
-
const CREATE_KEY_FN$
|
|
20971
|
+
const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20325
20972
|
const parts = [symbol, strategyName, exchangeName];
|
|
20326
20973
|
if (frameName)
|
|
20327
20974
|
parts.push(frameName);
|
|
@@ -20338,7 +20985,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20338
20985
|
* @param timestamp - Unix timestamp in milliseconds
|
|
20339
20986
|
* @returns Filename string
|
|
20340
20987
|
*/
|
|
20341
|
-
const CREATE_FILE_NAME_FN$
|
|
20988
|
+
const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20342
20989
|
const parts = [symbol, strategyName, exchangeName];
|
|
20343
20990
|
if (frameName) {
|
|
20344
20991
|
parts.push(frameName);
|
|
@@ -20358,7 +21005,7 @@ function percentile(sortedArray, p) {
|
|
|
20358
21005
|
return sortedArray[Math.max(0, index)];
|
|
20359
21006
|
}
|
|
20360
21007
|
/** Maximum number of performance events to store per strategy */
|
|
20361
|
-
const MAX_EVENTS$
|
|
21008
|
+
const MAX_EVENTS$7 = 10000;
|
|
20362
21009
|
/**
|
|
20363
21010
|
* Storage class for accumulating performance metrics per strategy.
|
|
20364
21011
|
* Maintains a list of all performance events and provides aggregated statistics.
|
|
@@ -20380,7 +21027,7 @@ class PerformanceStorage {
|
|
|
20380
21027
|
addEvent(event) {
|
|
20381
21028
|
this._events.unshift(event);
|
|
20382
21029
|
// Trim queue if exceeded MAX_EVENTS (keep most recent)
|
|
20383
|
-
if (this._events.length > MAX_EVENTS$
|
|
21030
|
+
if (this._events.length > MAX_EVENTS$7) {
|
|
20384
21031
|
this._events.pop();
|
|
20385
21032
|
}
|
|
20386
21033
|
}
|
|
@@ -20522,7 +21169,7 @@ class PerformanceStorage {
|
|
|
20522
21169
|
async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
20523
21170
|
const markdown = await this.getReport(strategyName, columns);
|
|
20524
21171
|
const timestamp = getContextTimestamp();
|
|
20525
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21172
|
+
const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20526
21173
|
await Markdown.writeData("performance", markdown, {
|
|
20527
21174
|
path,
|
|
20528
21175
|
file: filename,
|
|
@@ -20569,7 +21216,7 @@ class PerformanceMarkdownService {
|
|
|
20569
21216
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20570
21217
|
* Each combination gets its own isolated storage instance.
|
|
20571
21218
|
*/
|
|
20572
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21219
|
+
this.getStorage = functoolsKit.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));
|
|
20573
21220
|
/**
|
|
20574
21221
|
* Subscribes to performance emitter to receive performance events.
|
|
20575
21222
|
* Protected against multiple subscriptions.
|
|
@@ -20736,7 +21383,7 @@ class PerformanceMarkdownService {
|
|
|
20736
21383
|
payload,
|
|
20737
21384
|
});
|
|
20738
21385
|
if (payload) {
|
|
20739
|
-
const key = CREATE_KEY_FN$
|
|
21386
|
+
const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20740
21387
|
this.getStorage.clear(key);
|
|
20741
21388
|
}
|
|
20742
21389
|
else {
|
|
@@ -20750,7 +21397,7 @@ class PerformanceMarkdownService {
|
|
|
20750
21397
|
* Creates a filename for markdown report based on walker name.
|
|
20751
21398
|
* Filename format: "walkerName-timestamp.md"
|
|
20752
21399
|
*/
|
|
20753
|
-
const CREATE_FILE_NAME_FN$
|
|
21400
|
+
const CREATE_FILE_NAME_FN$7 = (walkerName, timestamp) => {
|
|
20754
21401
|
return `${walkerName}-${timestamp}.md`;
|
|
20755
21402
|
};
|
|
20756
21403
|
/**
|
|
@@ -20785,7 +21432,7 @@ function formatMetric(value) {
|
|
|
20785
21432
|
* Storage class for accumulating walker results.
|
|
20786
21433
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
20787
21434
|
*/
|
|
20788
|
-
let ReportStorage$
|
|
21435
|
+
let ReportStorage$6 = class ReportStorage {
|
|
20789
21436
|
constructor(walkerName) {
|
|
20790
21437
|
this.walkerName = walkerName;
|
|
20791
21438
|
/** Walker metadata (set from first addResult call) */
|
|
@@ -20973,7 +21620,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
20973
21620
|
async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
20974
21621
|
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
20975
21622
|
const timestamp = getContextTimestamp();
|
|
20976
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21623
|
+
const filename = CREATE_FILE_NAME_FN$7(this.walkerName, timestamp);
|
|
20977
21624
|
await Markdown.writeData("walker", markdown, {
|
|
20978
21625
|
path,
|
|
20979
21626
|
file: filename,
|
|
@@ -21009,7 +21656,7 @@ class WalkerMarkdownService {
|
|
|
21009
21656
|
* Memoized function to get or create ReportStorage for a walker.
|
|
21010
21657
|
* Each walker gets its own isolated storage instance.
|
|
21011
21658
|
*/
|
|
21012
|
-
this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$
|
|
21659
|
+
this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$6(walkerName));
|
|
21013
21660
|
/**
|
|
21014
21661
|
* Subscribes to walker emitter to receive walker progress events.
|
|
21015
21662
|
* Protected against multiple subscriptions.
|
|
@@ -21206,7 +21853,7 @@ class WalkerMarkdownService {
|
|
|
21206
21853
|
* @param backtest - Whether running in backtest mode
|
|
21207
21854
|
* @returns Unique string key for memoization
|
|
21208
21855
|
*/
|
|
21209
|
-
const CREATE_KEY_FN$
|
|
21856
|
+
const CREATE_KEY_FN$e = (exchangeName, frameName, backtest) => {
|
|
21210
21857
|
const parts = [exchangeName];
|
|
21211
21858
|
if (frameName)
|
|
21212
21859
|
parts.push(frameName);
|
|
@@ -21217,7 +21864,7 @@ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
|
|
|
21217
21864
|
* Creates a filename for markdown report based on memoization key components.
|
|
21218
21865
|
* Filename format: "strategyName_exchangeName_frameName-timestamp.md"
|
|
21219
21866
|
*/
|
|
21220
|
-
const CREATE_FILE_NAME_FN$
|
|
21867
|
+
const CREATE_FILE_NAME_FN$6 = (strategyName, exchangeName, frameName, timestamp) => {
|
|
21221
21868
|
const parts = [strategyName, exchangeName];
|
|
21222
21869
|
if (frameName) {
|
|
21223
21870
|
parts.push(frameName);
|
|
@@ -21250,7 +21897,7 @@ function isUnsafe(value) {
|
|
|
21250
21897
|
return false;
|
|
21251
21898
|
}
|
|
21252
21899
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
21253
|
-
const MAX_EVENTS$
|
|
21900
|
+
const MAX_EVENTS$6 = 250;
|
|
21254
21901
|
/**
|
|
21255
21902
|
* Storage class for accumulating closed signals per strategy and generating heatmap.
|
|
21256
21903
|
* Maintains symbol-level statistics and provides portfolio-wide metrics.
|
|
@@ -21276,7 +21923,7 @@ class HeatmapStorage {
|
|
|
21276
21923
|
const signals = this.symbolData.get(symbol);
|
|
21277
21924
|
signals.unshift(data);
|
|
21278
21925
|
// Trim queue if exceeded MAX_EVENTS per symbol
|
|
21279
|
-
if (signals.length > MAX_EVENTS$
|
|
21926
|
+
if (signals.length > MAX_EVENTS$6) {
|
|
21280
21927
|
signals.pop();
|
|
21281
21928
|
}
|
|
21282
21929
|
}
|
|
@@ -21527,7 +22174,7 @@ class HeatmapStorage {
|
|
|
21527
22174
|
async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
21528
22175
|
const markdown = await this.getReport(strategyName, columns);
|
|
21529
22176
|
const timestamp = getContextTimestamp();
|
|
21530
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
22177
|
+
const filename = CREATE_FILE_NAME_FN$6(strategyName, this.exchangeName, this.frameName, timestamp);
|
|
21531
22178
|
await Markdown.writeData("heat", markdown, {
|
|
21532
22179
|
path,
|
|
21533
22180
|
file: filename,
|
|
@@ -21573,7 +22220,7 @@ class HeatMarkdownService {
|
|
|
21573
22220
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
21574
22221
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
21575
22222
|
*/
|
|
21576
|
-
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22223
|
+
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
21577
22224
|
/**
|
|
21578
22225
|
* Subscribes to signal emitter to receive tick events.
|
|
21579
22226
|
* Protected against multiple subscriptions.
|
|
@@ -21768,7 +22415,7 @@ class HeatMarkdownService {
|
|
|
21768
22415
|
payload,
|
|
21769
22416
|
});
|
|
21770
22417
|
if (payload) {
|
|
21771
|
-
const key = CREATE_KEY_FN$
|
|
22418
|
+
const key = CREATE_KEY_FN$e(payload.exchangeName, payload.frameName, payload.backtest);
|
|
21772
22419
|
this.getStorage.clear(key);
|
|
21773
22420
|
}
|
|
21774
22421
|
else {
|
|
@@ -22799,7 +23446,7 @@ class ClientPartial {
|
|
|
22799
23446
|
* @param backtest - Whether running in backtest mode
|
|
22800
23447
|
* @returns Unique string key for memoization
|
|
22801
23448
|
*/
|
|
22802
|
-
const CREATE_KEY_FN$
|
|
23449
|
+
const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
22803
23450
|
/**
|
|
22804
23451
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
22805
23452
|
*
|
|
@@ -22921,7 +23568,7 @@ class PartialConnectionService {
|
|
|
22921
23568
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
22922
23569
|
* Value: ClientPartial instance with logger and event emitters
|
|
22923
23570
|
*/
|
|
22924
|
-
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
23571
|
+
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
|
|
22925
23572
|
return new ClientPartial({
|
|
22926
23573
|
signalId,
|
|
22927
23574
|
logger: this.loggerService,
|
|
@@ -23011,7 +23658,7 @@ class PartialConnectionService {
|
|
|
23011
23658
|
const partial = this.getPartial(data.id, backtest);
|
|
23012
23659
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
23013
23660
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
23014
|
-
const key = CREATE_KEY_FN$
|
|
23661
|
+
const key = CREATE_KEY_FN$d(data.id, backtest);
|
|
23015
23662
|
this.getPartial.clear(key);
|
|
23016
23663
|
};
|
|
23017
23664
|
}
|
|
@@ -23027,7 +23674,7 @@ class PartialConnectionService {
|
|
|
23027
23674
|
* @param backtest - Whether running in backtest mode
|
|
23028
23675
|
* @returns Unique string key for memoization
|
|
23029
23676
|
*/
|
|
23030
|
-
const CREATE_KEY_FN$
|
|
23677
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
23031
23678
|
const parts = [symbol, strategyName, exchangeName];
|
|
23032
23679
|
if (frameName)
|
|
23033
23680
|
parts.push(frameName);
|
|
@@ -23038,7 +23685,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
23038
23685
|
* Creates a filename for markdown report based on memoization key components.
|
|
23039
23686
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
23040
23687
|
*/
|
|
23041
|
-
const CREATE_FILE_NAME_FN$
|
|
23688
|
+
const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
23042
23689
|
const parts = [symbol, strategyName, exchangeName];
|
|
23043
23690
|
if (frameName) {
|
|
23044
23691
|
parts.push(frameName);
|
|
@@ -23049,12 +23696,12 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
23049
23696
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
23050
23697
|
};
|
|
23051
23698
|
/** Maximum number of events to store in partial reports */
|
|
23052
|
-
const MAX_EVENTS$
|
|
23699
|
+
const MAX_EVENTS$5 = 250;
|
|
23053
23700
|
/**
|
|
23054
23701
|
* Storage class for accumulating partial profit/loss events per symbol-strategy pair.
|
|
23055
23702
|
* Maintains a chronological list of profit and loss level events.
|
|
23056
23703
|
*/
|
|
23057
|
-
let ReportStorage$
|
|
23704
|
+
let ReportStorage$5 = class ReportStorage {
|
|
23058
23705
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
23059
23706
|
this.symbol = symbol;
|
|
23060
23707
|
this.strategyName = strategyName;
|
|
@@ -23097,7 +23744,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23097
23744
|
backtest,
|
|
23098
23745
|
});
|
|
23099
23746
|
// Trim queue if exceeded MAX_EVENTS
|
|
23100
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
23747
|
+
if (this._eventList.length > MAX_EVENTS$5) {
|
|
23101
23748
|
this._eventList.pop();
|
|
23102
23749
|
}
|
|
23103
23750
|
}
|
|
@@ -23135,7 +23782,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23135
23782
|
backtest,
|
|
23136
23783
|
});
|
|
23137
23784
|
// Trim queue if exceeded MAX_EVENTS
|
|
23138
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
23785
|
+
if (this._eventList.length > MAX_EVENTS$5) {
|
|
23139
23786
|
this._eventList.pop();
|
|
23140
23787
|
}
|
|
23141
23788
|
}
|
|
@@ -23211,7 +23858,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
23211
23858
|
async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
23212
23859
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
23213
23860
|
const timestamp = getContextTimestamp();
|
|
23214
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
23861
|
+
const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
23215
23862
|
await Markdown.writeData("partial", markdown, {
|
|
23216
23863
|
path,
|
|
23217
23864
|
file: filename,
|
|
@@ -23252,7 +23899,7 @@ class PartialMarkdownService {
|
|
|
23252
23899
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
23253
23900
|
* Each combination gets its own isolated storage instance.
|
|
23254
23901
|
*/
|
|
23255
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23902
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
23256
23903
|
/**
|
|
23257
23904
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
23258
23905
|
* Protected against multiple subscriptions.
|
|
@@ -23462,7 +24109,7 @@ class PartialMarkdownService {
|
|
|
23462
24109
|
payload,
|
|
23463
24110
|
});
|
|
23464
24111
|
if (payload) {
|
|
23465
|
-
const key = CREATE_KEY_FN$
|
|
24112
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
23466
24113
|
this.getStorage.clear(key);
|
|
23467
24114
|
}
|
|
23468
24115
|
else {
|
|
@@ -23478,7 +24125,7 @@ class PartialMarkdownService {
|
|
|
23478
24125
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
23479
24126
|
* @returns Unique string key for memoization
|
|
23480
24127
|
*/
|
|
23481
|
-
const CREATE_KEY_FN$
|
|
24128
|
+
const CREATE_KEY_FN$b = (context) => {
|
|
23482
24129
|
const parts = [context.strategyName, context.exchangeName];
|
|
23483
24130
|
if (context.frameName)
|
|
23484
24131
|
parts.push(context.frameName);
|
|
@@ -23552,7 +24199,7 @@ class PartialGlobalService {
|
|
|
23552
24199
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
23553
24200
|
* @param methodName - Name of the calling method for error tracking
|
|
23554
24201
|
*/
|
|
23555
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
24202
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
|
|
23556
24203
|
this.loggerService.log("partialGlobalService validate", {
|
|
23557
24204
|
context,
|
|
23558
24205
|
methodName,
|
|
@@ -24007,7 +24654,7 @@ class ClientBreakeven {
|
|
|
24007
24654
|
* @param backtest - Whether running in backtest mode
|
|
24008
24655
|
* @returns Unique string key for memoization
|
|
24009
24656
|
*/
|
|
24010
|
-
const CREATE_KEY_FN$
|
|
24657
|
+
const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
24011
24658
|
/**
|
|
24012
24659
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
24013
24660
|
*
|
|
@@ -24093,7 +24740,7 @@ class BreakevenConnectionService {
|
|
|
24093
24740
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24094
24741
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
24095
24742
|
*/
|
|
24096
|
-
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
24743
|
+
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
|
|
24097
24744
|
return new ClientBreakeven({
|
|
24098
24745
|
signalId,
|
|
24099
24746
|
logger: this.loggerService,
|
|
@@ -24154,7 +24801,7 @@ class BreakevenConnectionService {
|
|
|
24154
24801
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
24155
24802
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24156
24803
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
24157
|
-
const key = CREATE_KEY_FN$
|
|
24804
|
+
const key = CREATE_KEY_FN$a(data.id, backtest);
|
|
24158
24805
|
this.getBreakeven.clear(key);
|
|
24159
24806
|
};
|
|
24160
24807
|
}
|
|
@@ -24170,7 +24817,7 @@ class BreakevenConnectionService {
|
|
|
24170
24817
|
* @param backtest - Whether running in backtest mode
|
|
24171
24818
|
* @returns Unique string key for memoization
|
|
24172
24819
|
*/
|
|
24173
|
-
const CREATE_KEY_FN$
|
|
24820
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24174
24821
|
const parts = [symbol, strategyName, exchangeName];
|
|
24175
24822
|
if (frameName)
|
|
24176
24823
|
parts.push(frameName);
|
|
@@ -24181,7 +24828,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
24181
24828
|
* Creates a filename for markdown report based on memoization key components.
|
|
24182
24829
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
24183
24830
|
*/
|
|
24184
|
-
const CREATE_FILE_NAME_FN$
|
|
24831
|
+
const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
24185
24832
|
const parts = [symbol, strategyName, exchangeName];
|
|
24186
24833
|
if (frameName) {
|
|
24187
24834
|
parts.push(frameName);
|
|
@@ -24192,12 +24839,12 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
24192
24839
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
24193
24840
|
};
|
|
24194
24841
|
/** Maximum number of events to store in breakeven reports */
|
|
24195
|
-
const MAX_EVENTS$
|
|
24842
|
+
const MAX_EVENTS$4 = 250;
|
|
24196
24843
|
/**
|
|
24197
24844
|
* Storage class for accumulating breakeven events per symbol-strategy pair.
|
|
24198
24845
|
* Maintains a chronological list of breakeven events.
|
|
24199
24846
|
*/
|
|
24200
|
-
let ReportStorage$
|
|
24847
|
+
let ReportStorage$4 = class ReportStorage {
|
|
24201
24848
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
24202
24849
|
this.symbol = symbol;
|
|
24203
24850
|
this.strategyName = strategyName;
|
|
@@ -24238,7 +24885,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
24238
24885
|
backtest,
|
|
24239
24886
|
});
|
|
24240
24887
|
// Trim queue if exceeded MAX_EVENTS
|
|
24241
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
24888
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
24242
24889
|
this._eventList.pop();
|
|
24243
24890
|
}
|
|
24244
24891
|
}
|
|
@@ -24306,7 +24953,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
24306
24953
|
async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
|
|
24307
24954
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24308
24955
|
const timestamp = getContextTimestamp();
|
|
24309
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
24956
|
+
const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24310
24957
|
await Markdown.writeData("breakeven", markdown, {
|
|
24311
24958
|
path,
|
|
24312
24959
|
file: filename,
|
|
@@ -24347,7 +24994,7 @@ class BreakevenMarkdownService {
|
|
|
24347
24994
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24348
24995
|
* Each combination gets its own isolated storage instance.
|
|
24349
24996
|
*/
|
|
24350
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24997
|
+
this.getStorage = functoolsKit.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));
|
|
24351
24998
|
/**
|
|
24352
24999
|
* Subscribes to breakeven signal emitter to receive events.
|
|
24353
25000
|
* Protected against multiple subscriptions.
|
|
@@ -24536,7 +25183,7 @@ class BreakevenMarkdownService {
|
|
|
24536
25183
|
payload,
|
|
24537
25184
|
});
|
|
24538
25185
|
if (payload) {
|
|
24539
|
-
const key = CREATE_KEY_FN$
|
|
25186
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24540
25187
|
this.getStorage.clear(key);
|
|
24541
25188
|
}
|
|
24542
25189
|
else {
|
|
@@ -24552,7 +25199,7 @@ class BreakevenMarkdownService {
|
|
|
24552
25199
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
24553
25200
|
* @returns Unique string key for memoization
|
|
24554
25201
|
*/
|
|
24555
|
-
const CREATE_KEY_FN$
|
|
25202
|
+
const CREATE_KEY_FN$8 = (context) => {
|
|
24556
25203
|
const parts = [context.strategyName, context.exchangeName];
|
|
24557
25204
|
if (context.frameName)
|
|
24558
25205
|
parts.push(context.frameName);
|
|
@@ -24626,7 +25273,7 @@ class BreakevenGlobalService {
|
|
|
24626
25273
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
24627
25274
|
* @param methodName - Name of the calling method for error tracking
|
|
24628
25275
|
*/
|
|
24629
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
25276
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
|
|
24630
25277
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
24631
25278
|
context,
|
|
24632
25279
|
methodName,
|
|
@@ -24846,7 +25493,7 @@ class ConfigValidationService {
|
|
|
24846
25493
|
* @param backtest - Whether running in backtest mode
|
|
24847
25494
|
* @returns Unique string key for memoization
|
|
24848
25495
|
*/
|
|
24849
|
-
const CREATE_KEY_FN$
|
|
25496
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24850
25497
|
const parts = [symbol, strategyName, exchangeName];
|
|
24851
25498
|
if (frameName)
|
|
24852
25499
|
parts.push(frameName);
|
|
@@ -24857,7 +25504,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
24857
25504
|
* Creates a filename for markdown report based on memoization key components.
|
|
24858
25505
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
24859
25506
|
*/
|
|
24860
|
-
const CREATE_FILE_NAME_FN$
|
|
25507
|
+
const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
24861
25508
|
const parts = [symbol, strategyName, exchangeName];
|
|
24862
25509
|
if (frameName) {
|
|
24863
25510
|
parts.push(frameName);
|
|
@@ -24868,12 +25515,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
24868
25515
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
24869
25516
|
};
|
|
24870
25517
|
/** Maximum number of events to store in risk reports */
|
|
24871
|
-
const MAX_EVENTS$
|
|
25518
|
+
const MAX_EVENTS$3 = 250;
|
|
24872
25519
|
/**
|
|
24873
25520
|
* Storage class for accumulating risk rejection events per symbol-strategy pair.
|
|
24874
25521
|
* Maintains a chronological list of rejected signals due to risk limits.
|
|
24875
25522
|
*/
|
|
24876
|
-
let ReportStorage$
|
|
25523
|
+
let ReportStorage$3 = class ReportStorage {
|
|
24877
25524
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
24878
25525
|
this.symbol = symbol;
|
|
24879
25526
|
this.strategyName = strategyName;
|
|
@@ -24890,7 +25537,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
24890
25537
|
addRejectionEvent(event) {
|
|
24891
25538
|
this._eventList.unshift(event);
|
|
24892
25539
|
// Trim queue if exceeded MAX_EVENTS
|
|
24893
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
25540
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
24894
25541
|
this._eventList.pop();
|
|
24895
25542
|
}
|
|
24896
25543
|
}
|
|
@@ -24974,7 +25621,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
24974
25621
|
async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
24975
25622
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24976
25623
|
const timestamp = getContextTimestamp();
|
|
24977
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
25624
|
+
const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24978
25625
|
await Markdown.writeData("risk", markdown, {
|
|
24979
25626
|
path,
|
|
24980
25627
|
file: filename,
|
|
@@ -25015,7 +25662,7 @@ class RiskMarkdownService {
|
|
|
25015
25662
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
25016
25663
|
* Each combination gets its own isolated storage instance.
|
|
25017
25664
|
*/
|
|
25018
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25665
|
+
this.getStorage = functoolsKit.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));
|
|
25019
25666
|
/**
|
|
25020
25667
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
25021
25668
|
* Protected against multiple subscriptions.
|
|
@@ -25204,7 +25851,7 @@ class RiskMarkdownService {
|
|
|
25204
25851
|
payload,
|
|
25205
25852
|
});
|
|
25206
25853
|
if (payload) {
|
|
25207
|
-
const key = CREATE_KEY_FN$
|
|
25854
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25208
25855
|
this.getStorage.clear(key);
|
|
25209
25856
|
}
|
|
25210
25857
|
else {
|
|
@@ -25502,6 +26149,7 @@ const WILDCARD_TARGET = {
|
|
|
25502
26149
|
schedule: true,
|
|
25503
26150
|
walker: true,
|
|
25504
26151
|
sync: true,
|
|
26152
|
+
highest_profit: true,
|
|
25505
26153
|
};
|
|
25506
26154
|
/**
|
|
25507
26155
|
* Utility class for managing report services.
|
|
@@ -25539,7 +26187,7 @@ class ReportUtils {
|
|
|
25539
26187
|
*
|
|
25540
26188
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
25541
26189
|
*/
|
|
25542
|
-
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) => {
|
|
26190
|
+
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) => {
|
|
25543
26191
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
|
|
25544
26192
|
backtest: bt$1,
|
|
25545
26193
|
breakeven,
|
|
@@ -25587,6 +26235,9 @@ class ReportUtils {
|
|
|
25587
26235
|
if (sync) {
|
|
25588
26236
|
unList.push(bt.syncReportService.subscribe());
|
|
25589
26237
|
}
|
|
26238
|
+
if (highest_profit) {
|
|
26239
|
+
unList.push(bt.highestProfitReportService.subscribe());
|
|
26240
|
+
}
|
|
25590
26241
|
return functoolsKit.compose(...unList.map((un) => () => void un()));
|
|
25591
26242
|
};
|
|
25592
26243
|
/**
|
|
@@ -25625,7 +26276,7 @@ class ReportUtils {
|
|
|
25625
26276
|
* Report.disable();
|
|
25626
26277
|
* ```
|
|
25627
26278
|
*/
|
|
25628
|
-
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) => {
|
|
26279
|
+
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) => {
|
|
25629
26280
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
|
|
25630
26281
|
backtest: bt$1,
|
|
25631
26282
|
breakeven,
|
|
@@ -25672,6 +26323,9 @@ class ReportUtils {
|
|
|
25672
26323
|
if (sync) {
|
|
25673
26324
|
bt.syncReportService.unsubscribe();
|
|
25674
26325
|
}
|
|
26326
|
+
if (highest_profit) {
|
|
26327
|
+
bt.highestProfitReportService.unsubscribe();
|
|
26328
|
+
}
|
|
25675
26329
|
};
|
|
25676
26330
|
}
|
|
25677
26331
|
}
|
|
@@ -27879,6 +28533,60 @@ class SyncReportService {
|
|
|
27879
28533
|
}
|
|
27880
28534
|
}
|
|
27881
28535
|
|
|
28536
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE = "HighestProfitReportService.subscribe";
|
|
28537
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE = "HighestProfitReportService.unsubscribe";
|
|
28538
|
+
const HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK = "HighestProfitReportService.tick";
|
|
28539
|
+
/**
|
|
28540
|
+
* Service for logging highest profit events to the JSONL report database.
|
|
28541
|
+
*
|
|
28542
|
+
* Listens to highestProfitSubject and writes each new price record to
|
|
28543
|
+
* Report.writeData() for persistence and analytics.
|
|
28544
|
+
*/
|
|
28545
|
+
class HighestProfitReportService {
|
|
28546
|
+
constructor() {
|
|
28547
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
28548
|
+
this.tick = async (data) => {
|
|
28549
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK, { data });
|
|
28550
|
+
await Report.writeData("highest_profit", {
|
|
28551
|
+
timestamp: data.timestamp,
|
|
28552
|
+
symbol: data.symbol,
|
|
28553
|
+
strategyName: data.signal.strategyName,
|
|
28554
|
+
exchangeName: data.exchangeName,
|
|
28555
|
+
frameName: data.frameName,
|
|
28556
|
+
backtest: data.backtest,
|
|
28557
|
+
signalId: data.signal.id,
|
|
28558
|
+
position: data.signal.position,
|
|
28559
|
+
currentPrice: data.currentPrice,
|
|
28560
|
+
priceOpen: data.signal.priceOpen,
|
|
28561
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
28562
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
28563
|
+
}, {
|
|
28564
|
+
symbol: data.symbol,
|
|
28565
|
+
strategyName: data.signal.strategyName,
|
|
28566
|
+
exchangeName: data.exchangeName,
|
|
28567
|
+
frameName: data.frameName,
|
|
28568
|
+
signalId: data.signal.id,
|
|
28569
|
+
walkerName: "",
|
|
28570
|
+
});
|
|
28571
|
+
};
|
|
28572
|
+
this.subscribe = functoolsKit.singleshot(() => {
|
|
28573
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE);
|
|
28574
|
+
const unsub = highestProfitSubject.subscribe(this.tick);
|
|
28575
|
+
return () => {
|
|
28576
|
+
this.subscribe.clear();
|
|
28577
|
+
unsub();
|
|
28578
|
+
};
|
|
28579
|
+
});
|
|
28580
|
+
this.unsubscribe = async () => {
|
|
28581
|
+
this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE);
|
|
28582
|
+
if (this.subscribe.hasValue()) {
|
|
28583
|
+
const lastSubscription = this.subscribe();
|
|
28584
|
+
lastSubscription();
|
|
28585
|
+
}
|
|
28586
|
+
};
|
|
28587
|
+
}
|
|
28588
|
+
}
|
|
28589
|
+
|
|
27882
28590
|
/**
|
|
27883
28591
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
27884
28592
|
*
|
|
@@ -27892,7 +28600,7 @@ class SyncReportService {
|
|
|
27892
28600
|
* @returns Colon-separated key string for memoization
|
|
27893
28601
|
* @internal
|
|
27894
28602
|
*/
|
|
27895
|
-
const CREATE_KEY_FN$
|
|
28603
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
27896
28604
|
const parts = [symbol, strategyName, exchangeName];
|
|
27897
28605
|
if (frameName)
|
|
27898
28606
|
parts.push(frameName);
|
|
@@ -27912,7 +28620,7 @@ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
27912
28620
|
* @returns Underscore-separated filename with .md extension
|
|
27913
28621
|
* @internal
|
|
27914
28622
|
*/
|
|
27915
|
-
const CREATE_FILE_NAME_FN$
|
|
28623
|
+
const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
27916
28624
|
const parts = [symbol, strategyName, exchangeName];
|
|
27917
28625
|
if (frameName) {
|
|
27918
28626
|
parts.push(frameName);
|
|
@@ -27927,7 +28635,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
27927
28635
|
* Older events are discarded when this limit is exceeded.
|
|
27928
28636
|
* @internal
|
|
27929
28637
|
*/
|
|
27930
|
-
const MAX_EVENTS$
|
|
28638
|
+
const MAX_EVENTS$2 = 250;
|
|
27931
28639
|
/**
|
|
27932
28640
|
* In-memory storage for accumulating strategy events per symbol-strategy pair.
|
|
27933
28641
|
*
|
|
@@ -27940,7 +28648,7 @@ const MAX_EVENTS$1 = 250;
|
|
|
27940
28648
|
*
|
|
27941
28649
|
* @internal
|
|
27942
28650
|
*/
|
|
27943
|
-
let ReportStorage$
|
|
28651
|
+
let ReportStorage$2 = class ReportStorage {
|
|
27944
28652
|
/**
|
|
27945
28653
|
* Creates a new ReportStorage instance.
|
|
27946
28654
|
*
|
|
@@ -27966,7 +28674,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
27966
28674
|
*/
|
|
27967
28675
|
addEvent(event) {
|
|
27968
28676
|
this._eventList.unshift(event);
|
|
27969
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
28677
|
+
if (this._eventList.length > MAX_EVENTS$2) {
|
|
27970
28678
|
this._eventList.pop();
|
|
27971
28679
|
}
|
|
27972
28680
|
}
|
|
@@ -28071,7 +28779,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
28071
28779
|
async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
|
|
28072
28780
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
28073
28781
|
const timestamp = getContextTimestamp();
|
|
28074
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
28782
|
+
const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
28075
28783
|
await Markdown.writeData("strategy", markdown, {
|
|
28076
28784
|
path,
|
|
28077
28785
|
file: filename,
|
|
@@ -28140,7 +28848,7 @@ class StrategyMarkdownService {
|
|
|
28140
28848
|
*
|
|
28141
28849
|
* @internal
|
|
28142
28850
|
*/
|
|
28143
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
28851
|
+
this.getStorage = functoolsKit.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));
|
|
28144
28852
|
/**
|
|
28145
28853
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
28146
28854
|
*
|
|
@@ -28708,7 +29416,7 @@ class StrategyMarkdownService {
|
|
|
28708
29416
|
this.clear = async (payload) => {
|
|
28709
29417
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
28710
29418
|
if (payload) {
|
|
28711
|
-
const key = CREATE_KEY_FN$
|
|
29419
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
28712
29420
|
this.getStorage.clear(key);
|
|
28713
29421
|
}
|
|
28714
29422
|
else {
|
|
@@ -28816,7 +29524,7 @@ class StrategyMarkdownService {
|
|
|
28816
29524
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
28817
29525
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
28818
29526
|
*/
|
|
28819
|
-
const CREATE_KEY_FN$
|
|
29527
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
28820
29528
|
const parts = [symbol, strategyName, exchangeName];
|
|
28821
29529
|
if (frameName)
|
|
28822
29530
|
parts.push(frameName);
|
|
@@ -28827,7 +29535,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
28827
29535
|
* Creates a filename for the markdown report.
|
|
28828
29536
|
* Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
|
|
28829
29537
|
*/
|
|
28830
|
-
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
29538
|
+
const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
28831
29539
|
const parts = [symbol, strategyName, exchangeName];
|
|
28832
29540
|
if (frameName) {
|
|
28833
29541
|
parts.push(frameName);
|
|
@@ -28838,12 +29546,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
|
|
|
28838
29546
|
return `${parts.join("_")}-${timestamp}.md`;
|
|
28839
29547
|
};
|
|
28840
29548
|
/** Maximum number of events to store in sync reports */
|
|
28841
|
-
const MAX_EVENTS = 250;
|
|
29549
|
+
const MAX_EVENTS$1 = 250;
|
|
28842
29550
|
/**
|
|
28843
29551
|
* Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
|
|
28844
29552
|
* Maintains a chronological list of signal-open and signal-close events.
|
|
28845
29553
|
*/
|
|
28846
|
-
class ReportStorage {
|
|
29554
|
+
let ReportStorage$1 = class ReportStorage {
|
|
28847
29555
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
28848
29556
|
this.symbol = symbol;
|
|
28849
29557
|
this.strategyName = strategyName;
|
|
@@ -28853,7 +29561,7 @@ class ReportStorage {
|
|
|
28853
29561
|
}
|
|
28854
29562
|
addEvent(event) {
|
|
28855
29563
|
this._eventList.unshift(event);
|
|
28856
|
-
if (this._eventList.length > MAX_EVENTS) {
|
|
29564
|
+
if (this._eventList.length > MAX_EVENTS$1) {
|
|
28857
29565
|
this._eventList.pop();
|
|
28858
29566
|
}
|
|
28859
29567
|
}
|
|
@@ -28914,7 +29622,7 @@ class ReportStorage {
|
|
|
28914
29622
|
async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
|
|
28915
29623
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
28916
29624
|
const timestamp = getContextTimestamp();
|
|
28917
|
-
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
29625
|
+
const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
28918
29626
|
await Markdown.writeData("sync", markdown, {
|
|
28919
29627
|
path,
|
|
28920
29628
|
file: filename,
|
|
@@ -28925,7 +29633,7 @@ class ReportStorage {
|
|
|
28925
29633
|
frameName: this.frameName,
|
|
28926
29634
|
});
|
|
28927
29635
|
}
|
|
28928
|
-
}
|
|
29636
|
+
};
|
|
28929
29637
|
/**
|
|
28930
29638
|
* Service for generating and saving signal sync markdown reports.
|
|
28931
29639
|
*
|
|
@@ -28948,7 +29656,7 @@ class ReportStorage {
|
|
|
28948
29656
|
class SyncMarkdownService {
|
|
28949
29657
|
constructor() {
|
|
28950
29658
|
this.loggerService = inject(TYPES.loggerService);
|
|
28951
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
29659
|
+
this.getStorage = functoolsKit.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));
|
|
28952
29660
|
this.subscribe = functoolsKit.singleshot(() => {
|
|
28953
29661
|
this.loggerService.log("syncMarkdownService init");
|
|
28954
29662
|
const unsubscribe = syncSubject.subscribe(this.tick);
|
|
@@ -29022,6 +29730,189 @@ class SyncMarkdownService {
|
|
|
29022
29730
|
};
|
|
29023
29731
|
this.clear = async (payload) => {
|
|
29024
29732
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
29733
|
+
if (payload) {
|
|
29734
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29735
|
+
this.getStorage.clear(key);
|
|
29736
|
+
}
|
|
29737
|
+
else {
|
|
29738
|
+
this.getStorage.clear();
|
|
29739
|
+
}
|
|
29740
|
+
};
|
|
29741
|
+
}
|
|
29742
|
+
}
|
|
29743
|
+
|
|
29744
|
+
/**
|
|
29745
|
+
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
29746
|
+
*/
|
|
29747
|
+
const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29748
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29749
|
+
if (frameName)
|
|
29750
|
+
parts.push(frameName);
|
|
29751
|
+
parts.push(backtest ? "backtest" : "live");
|
|
29752
|
+
return parts.join(":");
|
|
29753
|
+
};
|
|
29754
|
+
/**
|
|
29755
|
+
* Creates a filename for the markdown report.
|
|
29756
|
+
*/
|
|
29757
|
+
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
29758
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29759
|
+
if (frameName) {
|
|
29760
|
+
parts.push(frameName);
|
|
29761
|
+
parts.push("backtest");
|
|
29762
|
+
}
|
|
29763
|
+
else
|
|
29764
|
+
parts.push("live");
|
|
29765
|
+
return `${parts.join("_")}-${timestamp}.md`;
|
|
29766
|
+
};
|
|
29767
|
+
/** Maximum number of events to store per combination */
|
|
29768
|
+
const MAX_EVENTS = 250;
|
|
29769
|
+
/**
|
|
29770
|
+
* Accumulates highest profit events per symbol-strategy-exchange-frame combination.
|
|
29771
|
+
*/
|
|
29772
|
+
class ReportStorage {
|
|
29773
|
+
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
29774
|
+
this.symbol = symbol;
|
|
29775
|
+
this.strategyName = strategyName;
|
|
29776
|
+
this.exchangeName = exchangeName;
|
|
29777
|
+
this.frameName = frameName;
|
|
29778
|
+
this._eventList = [];
|
|
29779
|
+
}
|
|
29780
|
+
/**
|
|
29781
|
+
* Adds a highest profit event to the storage.
|
|
29782
|
+
*/
|
|
29783
|
+
addEvent(data, currentPrice, backtest, timestamp) {
|
|
29784
|
+
this._eventList.unshift({
|
|
29785
|
+
timestamp,
|
|
29786
|
+
symbol: data.symbol,
|
|
29787
|
+
strategyName: data.strategyName,
|
|
29788
|
+
signalId: data.id,
|
|
29789
|
+
position: data.position,
|
|
29790
|
+
currentPrice,
|
|
29791
|
+
priceOpen: data.priceOpen,
|
|
29792
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
29793
|
+
priceStopLoss: data.priceStopLoss,
|
|
29794
|
+
backtest,
|
|
29795
|
+
});
|
|
29796
|
+
if (this._eventList.length > MAX_EVENTS) {
|
|
29797
|
+
this._eventList.pop();
|
|
29798
|
+
}
|
|
29799
|
+
}
|
|
29800
|
+
/**
|
|
29801
|
+
* Returns aggregated statistics from accumulated events.
|
|
29802
|
+
*/
|
|
29803
|
+
async getData() {
|
|
29804
|
+
return {
|
|
29805
|
+
eventList: this._eventList,
|
|
29806
|
+
totalEvents: this._eventList.length,
|
|
29807
|
+
};
|
|
29808
|
+
}
|
|
29809
|
+
/**
|
|
29810
|
+
* Generates a markdown report table for this storage.
|
|
29811
|
+
*/
|
|
29812
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.highest_profit_columns) {
|
|
29813
|
+
const stats = await this.getData();
|
|
29814
|
+
if (stats.totalEvents === 0) {
|
|
29815
|
+
return [
|
|
29816
|
+
`# Highest Profit Report: ${symbol}:${strategyName}`,
|
|
29817
|
+
"",
|
|
29818
|
+
"No highest profit events recorded yet.",
|
|
29819
|
+
].join("\n");
|
|
29820
|
+
}
|
|
29821
|
+
const visibleColumns = [];
|
|
29822
|
+
for (const col of columns) {
|
|
29823
|
+
if (await col.isVisible()) {
|
|
29824
|
+
visibleColumns.push(col);
|
|
29825
|
+
}
|
|
29826
|
+
}
|
|
29827
|
+
const header = visibleColumns.map((col) => col.label);
|
|
29828
|
+
const separator = visibleColumns.map(() => "---");
|
|
29829
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
29830
|
+
const tableData = [header, separator, ...rows];
|
|
29831
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
29832
|
+
return [
|
|
29833
|
+
`# Highest Profit Report: ${symbol}:${strategyName}`,
|
|
29834
|
+
"",
|
|
29835
|
+
table,
|
|
29836
|
+
"",
|
|
29837
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
29838
|
+
].join("\n");
|
|
29839
|
+
}
|
|
29840
|
+
/**
|
|
29841
|
+
* Saves the report to disk.
|
|
29842
|
+
*/
|
|
29843
|
+
async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
|
|
29844
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
29845
|
+
const timestamp = getContextTimestamp();
|
|
29846
|
+
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
29847
|
+
await Markdown.writeData("highest_profit", markdown, {
|
|
29848
|
+
path,
|
|
29849
|
+
file: filename,
|
|
29850
|
+
symbol: this.symbol,
|
|
29851
|
+
signalId: "",
|
|
29852
|
+
strategyName: this.strategyName,
|
|
29853
|
+
exchangeName: this.exchangeName,
|
|
29854
|
+
frameName: this.frameName,
|
|
29855
|
+
});
|
|
29856
|
+
}
|
|
29857
|
+
}
|
|
29858
|
+
/**
|
|
29859
|
+
* Service for generating and saving highest profit markdown reports.
|
|
29860
|
+
*
|
|
29861
|
+
* Listens to highestProfitSubject and accumulates events per
|
|
29862
|
+
* symbol-strategy-exchange-frame combination. Provides getData(),
|
|
29863
|
+
* getReport(), and dump() methods matching the Partial pattern.
|
|
29864
|
+
*/
|
|
29865
|
+
class HighestProfitMarkdownService {
|
|
29866
|
+
constructor() {
|
|
29867
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
29868
|
+
this.getStorage = functoolsKit.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));
|
|
29869
|
+
this.subscribe = functoolsKit.singleshot(() => {
|
|
29870
|
+
this.loggerService.log("highestProfitMarkdownService init");
|
|
29871
|
+
const unsub = highestProfitSubject.subscribe(this.tick);
|
|
29872
|
+
return () => {
|
|
29873
|
+
this.subscribe.clear();
|
|
29874
|
+
this.clear();
|
|
29875
|
+
unsub();
|
|
29876
|
+
};
|
|
29877
|
+
});
|
|
29878
|
+
this.unsubscribe = async () => {
|
|
29879
|
+
this.loggerService.log("highestProfitMarkdownService unsubscribe");
|
|
29880
|
+
if (this.subscribe.hasValue()) {
|
|
29881
|
+
const lastSubscription = this.subscribe();
|
|
29882
|
+
lastSubscription();
|
|
29883
|
+
}
|
|
29884
|
+
};
|
|
29885
|
+
this.tick = async (data) => {
|
|
29886
|
+
this.loggerService.log("highestProfitMarkdownService tick", { data });
|
|
29887
|
+
const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
|
|
29888
|
+
storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
|
|
29889
|
+
};
|
|
29890
|
+
this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29891
|
+
this.loggerService.log("highestProfitMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
29892
|
+
if (!this.subscribe.hasValue()) {
|
|
29893
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before getting data.");
|
|
29894
|
+
}
|
|
29895
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29896
|
+
return storage.getData();
|
|
29897
|
+
};
|
|
29898
|
+
this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.highest_profit_columns) => {
|
|
29899
|
+
this.loggerService.log("highestProfitMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
29900
|
+
if (!this.subscribe.hasValue()) {
|
|
29901
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before generating reports.");
|
|
29902
|
+
}
|
|
29903
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29904
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
29905
|
+
};
|
|
29906
|
+
this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) => {
|
|
29907
|
+
this.loggerService.log("highestProfitMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
|
|
29908
|
+
if (!this.subscribe.hasValue()) {
|
|
29909
|
+
throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before dumping reports.");
|
|
29910
|
+
}
|
|
29911
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
29912
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
29913
|
+
};
|
|
29914
|
+
this.clear = async (payload) => {
|
|
29915
|
+
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
29025
29916
|
if (payload) {
|
|
29026
29917
|
const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29027
29918
|
this.getStorage.clear(key);
|
|
@@ -29371,6 +30262,7 @@ class TimeMetaService {
|
|
|
29371
30262
|
provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
|
|
29372
30263
|
provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
|
|
29373
30264
|
provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
|
|
30265
|
+
provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
|
|
29374
30266
|
}
|
|
29375
30267
|
{
|
|
29376
30268
|
provide(TYPES.backtestReportService, () => new BacktestReportService());
|
|
@@ -29384,6 +30276,7 @@ class TimeMetaService {
|
|
|
29384
30276
|
provide(TYPES.riskReportService, () => new RiskReportService());
|
|
29385
30277
|
provide(TYPES.strategyReportService, () => new StrategyReportService());
|
|
29386
30278
|
provide(TYPES.syncReportService, () => new SyncReportService());
|
|
30279
|
+
provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
|
|
29387
30280
|
}
|
|
29388
30281
|
{
|
|
29389
30282
|
provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
|
|
@@ -29466,6 +30359,7 @@ const markdownServices = {
|
|
|
29466
30359
|
riskMarkdownService: inject(TYPES.riskMarkdownService),
|
|
29467
30360
|
strategyMarkdownService: inject(TYPES.strategyMarkdownService),
|
|
29468
30361
|
syncMarkdownService: inject(TYPES.syncMarkdownService),
|
|
30362
|
+
highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
|
|
29469
30363
|
};
|
|
29470
30364
|
const reportServices = {
|
|
29471
30365
|
backtestReportService: inject(TYPES.backtestReportService),
|
|
@@ -29479,6 +30373,7 @@ const reportServices = {
|
|
|
29479
30373
|
riskReportService: inject(TYPES.riskReportService),
|
|
29480
30374
|
strategyReportService: inject(TYPES.strategyReportService),
|
|
29481
30375
|
syncReportService: inject(TYPES.syncReportService),
|
|
30376
|
+
highestProfitReportService: inject(TYPES.highestProfitReportService),
|
|
29482
30377
|
};
|
|
29483
30378
|
const validationServices = {
|
|
29484
30379
|
exchangeValidationService: inject(TYPES.exchangeValidationService),
|
|
@@ -31250,7 +32145,7 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
|
|
|
31250
32145
|
*
|
|
31251
32146
|
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
31252
32147
|
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
31253
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32148
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31254
32149
|
* @returns percentShift to pass to `commitTrailingStop`
|
|
31255
32150
|
*
|
|
31256
32151
|
* @example
|
|
@@ -31272,7 +32167,7 @@ const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectiv
|
|
|
31272
32167
|
*
|
|
31273
32168
|
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
31274
32169
|
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
31275
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32170
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31276
32171
|
* @returns percentShift to pass to `commitTrailingTake`
|
|
31277
32172
|
*
|
|
31278
32173
|
* @example
|
|
@@ -31297,7 +32192,7 @@ const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effe
|
|
|
31297
32192
|
*
|
|
31298
32193
|
* @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
|
|
31299
32194
|
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
31300
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32195
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31301
32196
|
* @param position - Position direction: "long" or "short"
|
|
31302
32197
|
* @returns Absolute stop-loss price corresponding to the given percentShift
|
|
31303
32198
|
*
|
|
@@ -31324,7 +32219,7 @@ const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePri
|
|
|
31324
32219
|
*
|
|
31325
32220
|
* @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
|
|
31326
32221
|
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
31327
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32222
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31328
32223
|
* @param position - Position direction: "long" or "short"
|
|
31329
32224
|
* @returns Absolute take-profit price corresponding to the given percentShift
|
|
31330
32225
|
*
|
|
@@ -31363,7 +32258,7 @@ const percentToCloseCost = (percentToClose, investedCost) => {
|
|
|
31363
32258
|
* Breakeven moves the SL to the effective entry price (effectivePriceOpen).
|
|
31364
32259
|
* The value is the same regardless of position direction.
|
|
31365
32260
|
*
|
|
31366
|
-
* @param effectivePriceOpen - Effective entry price (from `
|
|
32261
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
|
|
31367
32262
|
* @returns New stop-loss price equal to the effective entry price
|
|
31368
32263
|
*
|
|
31369
32264
|
* @example
|
|
@@ -32285,13 +33180,22 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
|
|
|
32285
33180
|
const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
|
|
32286
33181
|
const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
|
|
32287
33182
|
const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
|
|
32288
|
-
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.
|
|
33183
|
+
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionEffectivePrice";
|
|
32289
33184
|
const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
|
|
32290
33185
|
const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
|
|
32291
33186
|
const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
32292
33187
|
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
32293
33188
|
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
32294
33189
|
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
33190
|
+
const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
|
|
33191
|
+
const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
|
|
33192
|
+
const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
|
|
33193
|
+
const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
|
|
33194
|
+
const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
|
|
33195
|
+
const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
|
|
33196
|
+
const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
|
|
33197
|
+
const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
|
|
33198
|
+
const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
|
|
32295
33199
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
32296
33200
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
32297
33201
|
/**
|
|
@@ -32558,7 +33462,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
32558
33462
|
if (!signal) {
|
|
32559
33463
|
return false;
|
|
32560
33464
|
}
|
|
32561
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33465
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32562
33466
|
if (effectivePriceOpen === null) {
|
|
32563
33467
|
return false;
|
|
32564
33468
|
}
|
|
@@ -32638,7 +33542,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
32638
33542
|
if (!signal) {
|
|
32639
33543
|
return false;
|
|
32640
33544
|
}
|
|
32641
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33545
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32642
33546
|
if (effectivePriceOpen === null) {
|
|
32643
33547
|
return false;
|
|
32644
33548
|
}
|
|
@@ -32688,7 +33592,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
|
32688
33592
|
if (!signal) {
|
|
32689
33593
|
return false;
|
|
32690
33594
|
}
|
|
32691
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33595
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32692
33596
|
if (effectivePriceOpen === null) {
|
|
32693
33597
|
return false;
|
|
32694
33598
|
}
|
|
@@ -32739,7 +33643,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
|
32739
33643
|
if (!signal) {
|
|
32740
33644
|
return false;
|
|
32741
33645
|
}
|
|
32742
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33646
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32743
33647
|
if (effectivePriceOpen === null) {
|
|
32744
33648
|
return false;
|
|
32745
33649
|
}
|
|
@@ -32801,7 +33705,7 @@ async function commitBreakeven(symbol) {
|
|
|
32801
33705
|
if (!signal) {
|
|
32802
33706
|
return false;
|
|
32803
33707
|
}
|
|
32804
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
33708
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32805
33709
|
if (effectivePriceOpen === null) {
|
|
32806
33710
|
return false;
|
|
32807
33711
|
}
|
|
@@ -33097,26 +34001,26 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
33097
34001
|
*
|
|
33098
34002
|
* @example
|
|
33099
34003
|
* ```typescript
|
|
33100
|
-
* import {
|
|
34004
|
+
* import { getPositionEffectivePrice } from "backtest-kit";
|
|
33101
34005
|
*
|
|
33102
|
-
* const avgPrice = await
|
|
34006
|
+
* const avgPrice = await getPositionEffectivePrice("BTCUSDT");
|
|
33103
34007
|
* // No DCA: avgPrice === priceOpen
|
|
33104
34008
|
* // After DCA at lower price: avgPrice < priceOpen
|
|
33105
34009
|
* ```
|
|
33106
34010
|
*/
|
|
33107
|
-
async function
|
|
34011
|
+
async function getPositionEffectivePrice(symbol) {
|
|
33108
34012
|
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
33109
34013
|
symbol,
|
|
33110
34014
|
});
|
|
33111
34015
|
if (!ExecutionContextService.hasContext()) {
|
|
33112
|
-
throw new Error("
|
|
34016
|
+
throw new Error("getPositionEffectivePrice requires an execution context");
|
|
33113
34017
|
}
|
|
33114
34018
|
if (!MethodContextService.hasContext()) {
|
|
33115
|
-
throw new Error("
|
|
34019
|
+
throw new Error("getPositionEffectivePrice requires a method context");
|
|
33116
34020
|
}
|
|
33117
34021
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33118
34022
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33119
|
-
return await bt.strategyCoreService.
|
|
34023
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33120
34024
|
}
|
|
33121
34025
|
/**
|
|
33122
34026
|
* Returns the number of DCA entries made for the current pending signal.
|
|
@@ -33471,6 +34375,284 @@ async function getPositionPartials(symbol) {
|
|
|
33471
34375
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33472
34376
|
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33473
34377
|
}
|
|
34378
|
+
/**
|
|
34379
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
34380
|
+
*
|
|
34381
|
+
* Each element represents a single position entry — the initial open or a subsequent
|
|
34382
|
+
* DCA entry added via commitAverageBuy.
|
|
34383
|
+
*
|
|
34384
|
+
* Returns null if no pending signal exists.
|
|
34385
|
+
* Returns a single-element array if no DCA entries were made.
|
|
34386
|
+
*
|
|
34387
|
+
* Each entry contains:
|
|
34388
|
+
* - `price` — execution price of this entry
|
|
34389
|
+
* - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
|
|
34390
|
+
*
|
|
34391
|
+
* @param symbol - Trading pair symbol
|
|
34392
|
+
* @returns Promise resolving to array of entry records or null
|
|
34393
|
+
*
|
|
34394
|
+
* @example
|
|
34395
|
+
* ```typescript
|
|
34396
|
+
* import { getPositionEntries } from "backtest-kit";
|
|
34397
|
+
*
|
|
34398
|
+
* const entries = await getPositionEntries("BTCUSDT");
|
|
34399
|
+
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
34400
|
+
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
34401
|
+
* ```
|
|
34402
|
+
*/
|
|
34403
|
+
async function getPositionEntries(symbol) {
|
|
34404
|
+
bt.loggerService.info(GET_POSITION_ENTRIES_METHOD_NAME, { symbol });
|
|
34405
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34406
|
+
throw new Error("getPositionEntries requires an execution context");
|
|
34407
|
+
}
|
|
34408
|
+
if (!MethodContextService.hasContext()) {
|
|
34409
|
+
throw new Error("getPositionEntries requires a method context");
|
|
34410
|
+
}
|
|
34411
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34412
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34413
|
+
return await bt.strategyCoreService.getPositionEntries(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34414
|
+
}
|
|
34415
|
+
/**
|
|
34416
|
+
* Returns the original estimated duration for the current pending signal.
|
|
34417
|
+
*
|
|
34418
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
34419
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
34420
|
+
*
|
|
34421
|
+
* Returns null if no pending signal exists.
|
|
34422
|
+
*
|
|
34423
|
+
* @param symbol - Trading pair symbol
|
|
34424
|
+
* @returns Promise resolving to estimated duration in minutes or null
|
|
34425
|
+
*
|
|
34426
|
+
* @example
|
|
34427
|
+
* ```typescript
|
|
34428
|
+
* import { getPositionEstimateMinutes } from "backtest-kit";
|
|
34429
|
+
*
|
|
34430
|
+
* const estimate = await getPositionEstimateMinutes("BTCUSDT");
|
|
34431
|
+
* // e.g. 120 (2 hours)
|
|
34432
|
+
* ```
|
|
34433
|
+
*/
|
|
34434
|
+
async function getPositionEstimateMinutes(symbol) {
|
|
34435
|
+
bt.loggerService.info(GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME, { symbol });
|
|
34436
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34437
|
+
throw new Error("getPositionEstimateMinutes requires an execution context");
|
|
34438
|
+
}
|
|
34439
|
+
if (!MethodContextService.hasContext()) {
|
|
34440
|
+
throw new Error("getPositionEstimateMinutes requires a method context");
|
|
34441
|
+
}
|
|
34442
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34443
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34444
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34445
|
+
}
|
|
34446
|
+
/**
|
|
34447
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
34448
|
+
*
|
|
34449
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
34450
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
34451
|
+
*
|
|
34452
|
+
* Returns null if no pending signal exists.
|
|
34453
|
+
*
|
|
34454
|
+
* @param symbol - Trading pair symbol
|
|
34455
|
+
* @returns Promise resolving to remaining minutes (≥ 0) or null
|
|
34456
|
+
*
|
|
34457
|
+
* @example
|
|
34458
|
+
* ```typescript
|
|
34459
|
+
* import { getPositionCountdownMinutes } from "backtest-kit";
|
|
34460
|
+
*
|
|
34461
|
+
* const remaining = await getPositionCountdownMinutes("BTCUSDT");
|
|
34462
|
+
* // e.g. 45 (45 minutes left)
|
|
34463
|
+
* // 0 when expired
|
|
34464
|
+
* ```
|
|
34465
|
+
*/
|
|
34466
|
+
async function getPositionCountdownMinutes(symbol) {
|
|
34467
|
+
bt.loggerService.info(GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME, { symbol });
|
|
34468
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34469
|
+
throw new Error("getPositionCountdownMinutes requires an execution context");
|
|
34470
|
+
}
|
|
34471
|
+
if (!MethodContextService.hasContext()) {
|
|
34472
|
+
throw new Error("getPositionCountdownMinutes requires a method context");
|
|
34473
|
+
}
|
|
34474
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34475
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34476
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34477
|
+
}
|
|
34478
|
+
/**
|
|
34479
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
34480
|
+
*
|
|
34481
|
+
* Initialized at position open with the entry price and timestamp.
|
|
34482
|
+
* Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
|
|
34483
|
+
* - LONG: tracks the highest price seen above effective entry
|
|
34484
|
+
* - SHORT: tracks the lowest price seen below effective entry
|
|
34485
|
+
*
|
|
34486
|
+
* Returns null if no pending signal exists.
|
|
34487
|
+
* Never returns null when a signal is active — always contains at least the entry price.
|
|
34488
|
+
*
|
|
34489
|
+
* @param symbol - Trading pair symbol
|
|
34490
|
+
* @returns Promise resolving to `{ price, timestamp }` record or null
|
|
34491
|
+
*
|
|
34492
|
+
* @example
|
|
34493
|
+
* ```typescript
|
|
34494
|
+
* import { getPositionHighestProfitPrice } from "backtest-kit";
|
|
34495
|
+
*
|
|
34496
|
+
* const peak = await getPositionHighestProfitPrice("BTCUSDT");
|
|
34497
|
+
* // e.g. { price: 44500, timestamp: 1700000000000 }
|
|
34498
|
+
* ```
|
|
34499
|
+
*/
|
|
34500
|
+
async function getPositionHighestProfitPrice(symbol) {
|
|
34501
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME, { symbol });
|
|
34502
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34503
|
+
throw new Error("getPositionHighestProfitPrice requires an execution context");
|
|
34504
|
+
}
|
|
34505
|
+
if (!MethodContextService.hasContext()) {
|
|
34506
|
+
throw new Error("getPositionHighestProfitPrice requires a method context");
|
|
34507
|
+
}
|
|
34508
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34509
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34510
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34511
|
+
}
|
|
34512
|
+
/**
|
|
34513
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
34514
|
+
*
|
|
34515
|
+
* Returns null if no pending signal exists.
|
|
34516
|
+
*
|
|
34517
|
+
* @param symbol - Trading pair symbol
|
|
34518
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
34519
|
+
*
|
|
34520
|
+
* @example
|
|
34521
|
+
* ```typescript
|
|
34522
|
+
* import { getPositionHighestProfitTimestamp } from "backtest-kit";
|
|
34523
|
+
*
|
|
34524
|
+
* const ts = await getPositionHighestProfitTimestamp("BTCUSDT");
|
|
34525
|
+
* // e.g. 1700000000000
|
|
34526
|
+
* ```
|
|
34527
|
+
*/
|
|
34528
|
+
async function getPositionHighestProfitTimestamp(symbol) {
|
|
34529
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME, { symbol });
|
|
34530
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34531
|
+
throw new Error("getPositionHighestProfitTimestamp requires an execution context");
|
|
34532
|
+
}
|
|
34533
|
+
if (!MethodContextService.hasContext()) {
|
|
34534
|
+
throw new Error("getPositionHighestProfitTimestamp requires a method context");
|
|
34535
|
+
}
|
|
34536
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34537
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34538
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34539
|
+
}
|
|
34540
|
+
/**
|
|
34541
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
34542
|
+
*
|
|
34543
|
+
* Returns null if no pending signal exists.
|
|
34544
|
+
*
|
|
34545
|
+
* @param symbol - Trading pair symbol
|
|
34546
|
+
* @returns Promise resolving to PnL percentage or null
|
|
34547
|
+
*
|
|
34548
|
+
* @example
|
|
34549
|
+
* ```typescript
|
|
34550
|
+
* import { getPositionHighestPnlPercentage } from "backtest-kit";
|
|
34551
|
+
*
|
|
34552
|
+
* const pnl = await getPositionHighestPnlPercentage("BTCUSDT");
|
|
34553
|
+
* // e.g. 3.5
|
|
34554
|
+
* ```
|
|
34555
|
+
*/
|
|
34556
|
+
async function getPositionHighestPnlPercentage(symbol) {
|
|
34557
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
34558
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34559
|
+
throw new Error("getPositionHighestPnlPercentage requires an execution context");
|
|
34560
|
+
}
|
|
34561
|
+
if (!MethodContextService.hasContext()) {
|
|
34562
|
+
throw new Error("getPositionHighestPnlPercentage requires a method context");
|
|
34563
|
+
}
|
|
34564
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34565
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34566
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34567
|
+
}
|
|
34568
|
+
/**
|
|
34569
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
34570
|
+
*
|
|
34571
|
+
* Returns null if no pending signal exists.
|
|
34572
|
+
*
|
|
34573
|
+
* @param symbol - Trading pair symbol
|
|
34574
|
+
* @returns Promise resolving to PnL cost or null
|
|
34575
|
+
*
|
|
34576
|
+
* @example
|
|
34577
|
+
* ```typescript
|
|
34578
|
+
* import { getPositionHighestPnlCost } from "backtest-kit";
|
|
34579
|
+
*
|
|
34580
|
+
* const pnlCost = await getPositionHighestPnlCost("BTCUSDT");
|
|
34581
|
+
* // e.g. 35.5
|
|
34582
|
+
* ```
|
|
34583
|
+
*/
|
|
34584
|
+
async function getPositionHighestPnlCost(symbol) {
|
|
34585
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME, { symbol });
|
|
34586
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34587
|
+
throw new Error("getPositionHighestPnlCost requires an execution context");
|
|
34588
|
+
}
|
|
34589
|
+
if (!MethodContextService.hasContext()) {
|
|
34590
|
+
throw new Error("getPositionHighestPnlCost requires a method context");
|
|
34591
|
+
}
|
|
34592
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34593
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34594
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34595
|
+
}
|
|
34596
|
+
/**
|
|
34597
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
34598
|
+
*
|
|
34599
|
+
* Returns null if no pending signal exists.
|
|
34600
|
+
*
|
|
34601
|
+
* @param symbol - Trading pair symbol
|
|
34602
|
+
* @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
|
|
34603
|
+
*
|
|
34604
|
+
* @example
|
|
34605
|
+
* ```typescript
|
|
34606
|
+
* import { getPositionHighestProfitBreakeven } from "backtest-kit";
|
|
34607
|
+
*
|
|
34608
|
+
* const couldBreakeven = await getPositionHighestProfitBreakeven("BTCUSDT");
|
|
34609
|
+
* // e.g. true (price reached the breakeven threshold at its peak)
|
|
34610
|
+
* ```
|
|
34611
|
+
*/
|
|
34612
|
+
async function getPositionHighestProfitBreakeven(symbol) {
|
|
34613
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME, { symbol });
|
|
34614
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34615
|
+
throw new Error("getPositionHighestProfitBreakeven requires an execution context");
|
|
34616
|
+
}
|
|
34617
|
+
if (!MethodContextService.hasContext()) {
|
|
34618
|
+
throw new Error("getPositionHighestProfitBreakeven requires a method context");
|
|
34619
|
+
}
|
|
34620
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34621
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34622
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34623
|
+
}
|
|
34624
|
+
/**
|
|
34625
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
34626
|
+
*
|
|
34627
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
34628
|
+
* Zero when called at the exact moment the peak was set.
|
|
34629
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
34630
|
+
*
|
|
34631
|
+
* Returns null if no pending signal exists.
|
|
34632
|
+
*
|
|
34633
|
+
* @param symbol - Trading pair symbol
|
|
34634
|
+
* @returns Promise resolving to drawdown duration in minutes or null
|
|
34635
|
+
*
|
|
34636
|
+
* @example
|
|
34637
|
+
* ```typescript
|
|
34638
|
+
* import { getPositionDrawdownMinutes } from "backtest-kit";
|
|
34639
|
+
*
|
|
34640
|
+
* const drawdown = await getPositionDrawdownMinutes("BTCUSDT");
|
|
34641
|
+
* // e.g. 30 (30 minutes since the highest profit price)
|
|
34642
|
+
* ```
|
|
34643
|
+
*/
|
|
34644
|
+
async function getPositionDrawdownMinutes(symbol) {
|
|
34645
|
+
bt.loggerService.info(GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
|
|
34646
|
+
if (!ExecutionContextService.hasContext()) {
|
|
34647
|
+
throw new Error("getPositionDrawdownMinutes requires an execution context");
|
|
34648
|
+
}
|
|
34649
|
+
if (!MethodContextService.hasContext()) {
|
|
34650
|
+
throw new Error("getPositionDrawdownMinutes requires a method context");
|
|
34651
|
+
}
|
|
34652
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
34653
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
34654
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
34655
|
+
}
|
|
33474
34656
|
/**
|
|
33475
34657
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
33476
34658
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -33648,6 +34830,8 @@ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
|
|
|
33648
34830
|
const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
|
|
33649
34831
|
const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
|
|
33650
34832
|
const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
|
|
34833
|
+
const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
|
|
34834
|
+
const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
|
|
33651
34835
|
/**
|
|
33652
34836
|
* Subscribes to all signal events with queued async processing.
|
|
33653
34837
|
*
|
|
@@ -34774,6 +35958,33 @@ function listenSyncOnce(filterFn, fn) {
|
|
|
34774
35958
|
bt.loggerService.log(LISTEN_SYNC_ONCE_METHOD_NAME);
|
|
34775
35959
|
return syncSubject.filter(filterFn).once(fn);
|
|
34776
35960
|
}
|
|
35961
|
+
/**
|
|
35962
|
+
* Subscribes to highest profit events with queued async processing.
|
|
35963
|
+
* Emits when a signal reaches a new highest profit level during its lifecycle.
|
|
35964
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
35965
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
35966
|
+
* Useful for tracking profit milestones and implementing dynamic management logic.
|
|
35967
|
+
*
|
|
35968
|
+
* @param fn - Callback function to handle highest profit events
|
|
35969
|
+
* @return Unsubscribe function to stop listening to events
|
|
35970
|
+
*/
|
|
35971
|
+
function listenHighestProfit(fn) {
|
|
35972
|
+
bt.loggerService.log(LISTEN_HIGHEST_PROFIT_METHOD_NAME);
|
|
35973
|
+
return highestProfitSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
|
|
35974
|
+
}
|
|
35975
|
+
/**
|
|
35976
|
+
* Subscribes to filtered highest profit events with one-time execution.
|
|
35977
|
+
* Listens for events matching the filter predicate, then executes callback once
|
|
35978
|
+
* and automatically unsubscribes. Useful for waiting for specific profit conditions.
|
|
35979
|
+
*
|
|
35980
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
35981
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
35982
|
+
* @returns Unsubscribe function to cancel the listener before it fires
|
|
35983
|
+
*/
|
|
35984
|
+
function listenHighestProfitOnce(filterFn, fn) {
|
|
35985
|
+
bt.loggerService.log(LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME);
|
|
35986
|
+
return highestProfitSubject.filter(filterFn).once(fn);
|
|
35987
|
+
}
|
|
34777
35988
|
|
|
34778
35989
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
34779
35990
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
@@ -34787,7 +35998,7 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
|
|
|
34787
35998
|
const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
|
|
34788
35999
|
const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
|
|
34789
36000
|
const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
|
|
34790
|
-
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.
|
|
36001
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionEffectivePrice";
|
|
34791
36002
|
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
|
|
34792
36003
|
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
|
|
34793
36004
|
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
|
|
@@ -34795,6 +36006,14 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
|
|
|
34795
36006
|
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
34796
36007
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
34797
36008
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
|
|
36009
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
|
|
36010
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
|
|
36011
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
|
|
36012
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
|
|
36013
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
|
|
36014
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
|
|
36015
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
|
|
36016
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
|
|
34798
36017
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
34799
36018
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
34800
36019
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -35367,7 +36586,7 @@ class BacktestUtils {
|
|
|
35367
36586
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35368
36587
|
* @returns Effective entry price, or null if no active position
|
|
35369
36588
|
*/
|
|
35370
|
-
this.
|
|
36589
|
+
this.getPositionEffectivePrice = async (symbol, context) => {
|
|
35371
36590
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
35372
36591
|
symbol,
|
|
35373
36592
|
context,
|
|
@@ -35383,7 +36602,7 @@ class BacktestUtils {
|
|
|
35383
36602
|
actions &&
|
|
35384
36603
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
35385
36604
|
}
|
|
35386
|
-
return await bt.strategyCoreService.
|
|
36605
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
35387
36606
|
};
|
|
35388
36607
|
/**
|
|
35389
36608
|
* Returns the total number of base-asset units currently held in the position.
|
|
@@ -35601,6 +36820,230 @@ class BacktestUtils {
|
|
|
35601
36820
|
}
|
|
35602
36821
|
return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
|
|
35603
36822
|
};
|
|
36823
|
+
/**
|
|
36824
|
+
* Returns the original estimated duration for the current pending signal.
|
|
36825
|
+
*
|
|
36826
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
36827
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
36828
|
+
*
|
|
36829
|
+
* Returns null if no pending signal exists.
|
|
36830
|
+
*
|
|
36831
|
+
* @param symbol - Trading pair symbol
|
|
36832
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36833
|
+
* @returns Estimated duration in minutes, or null if no active position
|
|
36834
|
+
*/
|
|
36835
|
+
this.getPositionEstimateMinutes = async (symbol, context) => {
|
|
36836
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
|
|
36837
|
+
symbol,
|
|
36838
|
+
context,
|
|
36839
|
+
});
|
|
36840
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36841
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36842
|
+
{
|
|
36843
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36844
|
+
riskName &&
|
|
36845
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
36846
|
+
riskList &&
|
|
36847
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
36848
|
+
actions &&
|
|
36849
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
36850
|
+
}
|
|
36851
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(true, symbol, context);
|
|
36852
|
+
};
|
|
36853
|
+
/**
|
|
36854
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
36855
|
+
*
|
|
36856
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
36857
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
36858
|
+
*
|
|
36859
|
+
* Returns null if no pending signal exists.
|
|
36860
|
+
*
|
|
36861
|
+
* @param symbol - Trading pair symbol
|
|
36862
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36863
|
+
* @returns Remaining minutes (≥ 0), or null if no active position
|
|
36864
|
+
*/
|
|
36865
|
+
this.getPositionCountdownMinutes = async (symbol, context) => {
|
|
36866
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
|
|
36867
|
+
symbol,
|
|
36868
|
+
context,
|
|
36869
|
+
});
|
|
36870
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36871
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36872
|
+
{
|
|
36873
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36874
|
+
riskName &&
|
|
36875
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
36876
|
+
riskList &&
|
|
36877
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
36878
|
+
actions &&
|
|
36879
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
36880
|
+
}
|
|
36881
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
|
|
36882
|
+
};
|
|
36883
|
+
/**
|
|
36884
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
36885
|
+
*
|
|
36886
|
+
* Returns null if no pending signal exists.
|
|
36887
|
+
*
|
|
36888
|
+
* @param symbol - Trading pair symbol
|
|
36889
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36890
|
+
* @returns price or null if no active position
|
|
36891
|
+
*/
|
|
36892
|
+
this.getPositionHighestProfitPrice = async (symbol, context) => {
|
|
36893
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
|
|
36894
|
+
symbol,
|
|
36895
|
+
context,
|
|
36896
|
+
});
|
|
36897
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36898
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36899
|
+
{
|
|
36900
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36901
|
+
riskName &&
|
|
36902
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
36903
|
+
riskList &&
|
|
36904
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
36905
|
+
actions &&
|
|
36906
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
36907
|
+
}
|
|
36908
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(true, symbol, context);
|
|
36909
|
+
};
|
|
36910
|
+
/**
|
|
36911
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
36912
|
+
*
|
|
36913
|
+
* Returns null if no pending signal exists.
|
|
36914
|
+
*
|
|
36915
|
+
* @param symbol - Trading pair symbol
|
|
36916
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36917
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
36918
|
+
*/
|
|
36919
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context) => {
|
|
36920
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
|
|
36921
|
+
symbol,
|
|
36922
|
+
context,
|
|
36923
|
+
});
|
|
36924
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36925
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36926
|
+
{
|
|
36927
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36928
|
+
riskName &&
|
|
36929
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
36930
|
+
riskList &&
|
|
36931
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
36932
|
+
actions &&
|
|
36933
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
36934
|
+
}
|
|
36935
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(true, symbol, context);
|
|
36936
|
+
};
|
|
36937
|
+
/**
|
|
36938
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
36939
|
+
*
|
|
36940
|
+
* Returns null if no pending signal exists.
|
|
36941
|
+
*
|
|
36942
|
+
* @param symbol - Trading pair symbol
|
|
36943
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36944
|
+
* @returns PnL percentage or null if no active position
|
|
36945
|
+
*/
|
|
36946
|
+
this.getPositionHighestPnlPercentage = async (symbol, context) => {
|
|
36947
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
|
|
36948
|
+
symbol,
|
|
36949
|
+
context,
|
|
36950
|
+
});
|
|
36951
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36952
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36953
|
+
{
|
|
36954
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36955
|
+
riskName &&
|
|
36956
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
36957
|
+
riskList &&
|
|
36958
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
36959
|
+
actions &&
|
|
36960
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
36961
|
+
}
|
|
36962
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(true, symbol, context);
|
|
36963
|
+
};
|
|
36964
|
+
/**
|
|
36965
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
36966
|
+
*
|
|
36967
|
+
* Returns null if no pending signal exists.
|
|
36968
|
+
*
|
|
36969
|
+
* @param symbol - Trading pair symbol
|
|
36970
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36971
|
+
* @returns PnL cost or null if no active position
|
|
36972
|
+
*/
|
|
36973
|
+
this.getPositionHighestPnlCost = async (symbol, context) => {
|
|
36974
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
|
|
36975
|
+
symbol,
|
|
36976
|
+
context,
|
|
36977
|
+
});
|
|
36978
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36979
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36980
|
+
{
|
|
36981
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36982
|
+
riskName &&
|
|
36983
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
36984
|
+
riskList &&
|
|
36985
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
36986
|
+
actions &&
|
|
36987
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
36988
|
+
}
|
|
36989
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(true, symbol, context);
|
|
36990
|
+
};
|
|
36991
|
+
/**
|
|
36992
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
36993
|
+
*
|
|
36994
|
+
* @param symbol - Trading pair symbol
|
|
36995
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
36996
|
+
* @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
|
|
36997
|
+
*/
|
|
36998
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context) => {
|
|
36999
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
|
|
37000
|
+
symbol,
|
|
37001
|
+
context,
|
|
37002
|
+
});
|
|
37003
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
37004
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
37005
|
+
{
|
|
37006
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37007
|
+
riskName &&
|
|
37008
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
37009
|
+
riskList &&
|
|
37010
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
37011
|
+
actions &&
|
|
37012
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
37013
|
+
}
|
|
37014
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(true, symbol, context);
|
|
37015
|
+
};
|
|
37016
|
+
/**
|
|
37017
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
37018
|
+
*
|
|
37019
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
37020
|
+
* Zero when called at the exact moment the peak was set.
|
|
37021
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
37022
|
+
*
|
|
37023
|
+
* Returns null if no pending signal exists.
|
|
37024
|
+
*
|
|
37025
|
+
* @param symbol - Trading pair symbol
|
|
37026
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
37027
|
+
* @returns Drawdown duration in minutes, or null if no active position
|
|
37028
|
+
*/
|
|
37029
|
+
this.getPositionDrawdownMinutes = async (symbol, context) => {
|
|
37030
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
|
|
37031
|
+
symbol,
|
|
37032
|
+
context,
|
|
37033
|
+
});
|
|
37034
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37035
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37036
|
+
{
|
|
37037
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37038
|
+
riskName &&
|
|
37039
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
37040
|
+
riskList &&
|
|
37041
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
37042
|
+
actions &&
|
|
37043
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
37044
|
+
}
|
|
37045
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
|
|
37046
|
+
};
|
|
35604
37047
|
/**
|
|
35605
37048
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
35606
37049
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -36162,7 +37605,7 @@ class BacktestUtils {
|
|
|
36162
37605
|
if (!signal) {
|
|
36163
37606
|
return false;
|
|
36164
37607
|
}
|
|
36165
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37608
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36166
37609
|
if (effectivePriceOpen === null) {
|
|
36167
37610
|
return false;
|
|
36168
37611
|
}
|
|
@@ -36247,7 +37690,7 @@ class BacktestUtils {
|
|
|
36247
37690
|
if (!signal) {
|
|
36248
37691
|
return false;
|
|
36249
37692
|
}
|
|
36250
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37693
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36251
37694
|
if (effectivePriceOpen === null) {
|
|
36252
37695
|
return false;
|
|
36253
37696
|
}
|
|
@@ -36300,7 +37743,7 @@ class BacktestUtils {
|
|
|
36300
37743
|
if (!signal) {
|
|
36301
37744
|
return false;
|
|
36302
37745
|
}
|
|
36303
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37746
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36304
37747
|
if (effectivePriceOpen === null) {
|
|
36305
37748
|
return false;
|
|
36306
37749
|
}
|
|
@@ -36354,7 +37797,7 @@ class BacktestUtils {
|
|
|
36354
37797
|
if (!signal) {
|
|
36355
37798
|
return false;
|
|
36356
37799
|
}
|
|
36357
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37800
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36358
37801
|
if (effectivePriceOpen === null) {
|
|
36359
37802
|
return false;
|
|
36360
37803
|
}
|
|
@@ -36416,7 +37859,7 @@ class BacktestUtils {
|
|
|
36416
37859
|
if (!signal) {
|
|
36417
37860
|
return false;
|
|
36418
37861
|
}
|
|
36419
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
37862
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
|
|
36420
37863
|
if (effectivePriceOpen === null) {
|
|
36421
37864
|
return false;
|
|
36422
37865
|
}
|
|
@@ -36704,7 +38147,7 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
|
|
|
36704
38147
|
const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
|
|
36705
38148
|
const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
|
|
36706
38149
|
const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
|
|
36707
|
-
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.
|
|
38150
|
+
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionEffectivePrice";
|
|
36708
38151
|
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
|
|
36709
38152
|
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
|
|
36710
38153
|
const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
|
|
@@ -36712,6 +38155,14 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
|
36712
38155
|
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
36713
38156
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
36714
38157
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
|
|
38158
|
+
const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
|
|
38159
|
+
const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
|
|
38160
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
|
|
38161
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
|
|
38162
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
|
|
38163
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
|
|
38164
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
|
|
38165
|
+
const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
|
|
36715
38166
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
36716
38167
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
36717
38168
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -37315,7 +38766,7 @@ class LiveUtils {
|
|
|
37315
38766
|
* @param context - Execution context with strategyName and exchangeName
|
|
37316
38767
|
* @returns Effective entry price, or null if no active position
|
|
37317
38768
|
*/
|
|
37318
|
-
this.
|
|
38769
|
+
this.getPositionEffectivePrice = async (symbol, context) => {
|
|
37319
38770
|
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
37320
38771
|
symbol,
|
|
37321
38772
|
context,
|
|
@@ -37331,7 +38782,7 @@ class LiveUtils {
|
|
|
37331
38782
|
actions &&
|
|
37332
38783
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
37333
38784
|
}
|
|
37334
|
-
return await bt.strategyCoreService.
|
|
38785
|
+
return await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
37335
38786
|
strategyName: context.strategyName,
|
|
37336
38787
|
exchangeName: context.exchangeName,
|
|
37337
38788
|
frameName: "",
|
|
@@ -37581,6 +39032,262 @@ class LiveUtils {
|
|
|
37581
39032
|
frameName: "",
|
|
37582
39033
|
});
|
|
37583
39034
|
};
|
|
39035
|
+
/**
|
|
39036
|
+
* Returns the original estimated duration for the current pending signal.
|
|
39037
|
+
*
|
|
39038
|
+
* Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
|
|
39039
|
+
* number of minutes the position is expected to be active before `time_expired`.
|
|
39040
|
+
*
|
|
39041
|
+
* Returns null if no pending signal exists.
|
|
39042
|
+
*
|
|
39043
|
+
* @param symbol - Trading pair symbol
|
|
39044
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39045
|
+
* @returns Estimated duration in minutes, or null if no active position
|
|
39046
|
+
*/
|
|
39047
|
+
this.getPositionEstimateMinutes = async (symbol, context) => {
|
|
39048
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
|
|
39049
|
+
symbol,
|
|
39050
|
+
context,
|
|
39051
|
+
});
|
|
39052
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39053
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39054
|
+
{
|
|
39055
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39056
|
+
riskName &&
|
|
39057
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
|
|
39058
|
+
riskList &&
|
|
39059
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
39060
|
+
actions &&
|
|
39061
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
|
|
39062
|
+
}
|
|
39063
|
+
return await bt.strategyCoreService.getPositionEstimateMinutes(false, symbol, {
|
|
39064
|
+
strategyName: context.strategyName,
|
|
39065
|
+
exchangeName: context.exchangeName,
|
|
39066
|
+
frameName: "",
|
|
39067
|
+
});
|
|
39068
|
+
};
|
|
39069
|
+
/**
|
|
39070
|
+
* Returns the remaining time before the position expires, clamped to zero.
|
|
39071
|
+
*
|
|
39072
|
+
* Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
|
|
39073
|
+
* Returns 0 once the estimate is exceeded (never negative).
|
|
39074
|
+
*
|
|
39075
|
+
* Returns null if no pending signal exists.
|
|
39076
|
+
*
|
|
39077
|
+
* @param symbol - Trading pair symbol
|
|
39078
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39079
|
+
* @returns Remaining minutes (≥ 0), or null if no active position
|
|
39080
|
+
*/
|
|
39081
|
+
this.getPositionCountdownMinutes = async (symbol, context) => {
|
|
39082
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
|
|
39083
|
+
symbol,
|
|
39084
|
+
context,
|
|
39085
|
+
});
|
|
39086
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39087
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39088
|
+
{
|
|
39089
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39090
|
+
riskName &&
|
|
39091
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
|
|
39092
|
+
riskList &&
|
|
39093
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
39094
|
+
actions &&
|
|
39095
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
|
|
39096
|
+
}
|
|
39097
|
+
return await bt.strategyCoreService.getPositionCountdownMinutes(false, symbol, {
|
|
39098
|
+
strategyName: context.strategyName,
|
|
39099
|
+
exchangeName: context.exchangeName,
|
|
39100
|
+
frameName: "",
|
|
39101
|
+
});
|
|
39102
|
+
};
|
|
39103
|
+
/**
|
|
39104
|
+
* Returns the best price reached in the profit direction during this position's life.
|
|
39105
|
+
*
|
|
39106
|
+
* Returns null if no pending signal exists.
|
|
39107
|
+
*
|
|
39108
|
+
* @param symbol - Trading pair symbol
|
|
39109
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39110
|
+
* @returns price or null if no active position
|
|
39111
|
+
*/
|
|
39112
|
+
this.getPositionHighestProfitPrice = async (symbol, context) => {
|
|
39113
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
|
|
39114
|
+
symbol,
|
|
39115
|
+
context,
|
|
39116
|
+
});
|
|
39117
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39118
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39119
|
+
{
|
|
39120
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39121
|
+
riskName &&
|
|
39122
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
|
|
39123
|
+
riskList &&
|
|
39124
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
39125
|
+
actions &&
|
|
39126
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
|
|
39127
|
+
}
|
|
39128
|
+
return await bt.strategyCoreService.getPositionHighestProfitPrice(false, symbol, {
|
|
39129
|
+
strategyName: context.strategyName,
|
|
39130
|
+
exchangeName: context.exchangeName,
|
|
39131
|
+
frameName: "",
|
|
39132
|
+
});
|
|
39133
|
+
};
|
|
39134
|
+
/**
|
|
39135
|
+
* Returns the timestamp when the best profit price was recorded during this position's life.
|
|
39136
|
+
*
|
|
39137
|
+
* Returns null if no pending signal exists.
|
|
39138
|
+
*
|
|
39139
|
+
* @param symbol - Trading pair symbol
|
|
39140
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39141
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
39142
|
+
*/
|
|
39143
|
+
this.getPositionHighestProfitTimestamp = async (symbol, context) => {
|
|
39144
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
|
|
39145
|
+
symbol,
|
|
39146
|
+
context,
|
|
39147
|
+
});
|
|
39148
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39149
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39150
|
+
{
|
|
39151
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39152
|
+
riskName &&
|
|
39153
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
|
|
39154
|
+
riskList &&
|
|
39155
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
39156
|
+
actions &&
|
|
39157
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
|
|
39158
|
+
}
|
|
39159
|
+
return await bt.strategyCoreService.getPositionHighestProfitTimestamp(false, symbol, {
|
|
39160
|
+
strategyName: context.strategyName,
|
|
39161
|
+
exchangeName: context.exchangeName,
|
|
39162
|
+
frameName: "",
|
|
39163
|
+
});
|
|
39164
|
+
};
|
|
39165
|
+
/**
|
|
39166
|
+
* Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
|
|
39167
|
+
*
|
|
39168
|
+
* Returns null if no pending signal exists.
|
|
39169
|
+
*
|
|
39170
|
+
* @param symbol - Trading pair symbol
|
|
39171
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39172
|
+
* @returns PnL percentage or null if no active position
|
|
39173
|
+
*/
|
|
39174
|
+
this.getPositionHighestPnlPercentage = async (symbol, context) => {
|
|
39175
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
|
|
39176
|
+
symbol,
|
|
39177
|
+
context,
|
|
39178
|
+
});
|
|
39179
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39180
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39181
|
+
{
|
|
39182
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39183
|
+
riskName &&
|
|
39184
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
|
|
39185
|
+
riskList &&
|
|
39186
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
39187
|
+
actions &&
|
|
39188
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
|
|
39189
|
+
}
|
|
39190
|
+
return await bt.strategyCoreService.getPositionHighestPnlPercentage(false, symbol, {
|
|
39191
|
+
strategyName: context.strategyName,
|
|
39192
|
+
exchangeName: context.exchangeName,
|
|
39193
|
+
frameName: "",
|
|
39194
|
+
});
|
|
39195
|
+
};
|
|
39196
|
+
/**
|
|
39197
|
+
* Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
|
|
39198
|
+
*
|
|
39199
|
+
* Returns null if no pending signal exists.
|
|
39200
|
+
*
|
|
39201
|
+
* @param symbol - Trading pair symbol
|
|
39202
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39203
|
+
* @returns PnL cost or null if no active position
|
|
39204
|
+
*/
|
|
39205
|
+
this.getPositionHighestPnlCost = async (symbol, context) => {
|
|
39206
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
|
|
39207
|
+
symbol,
|
|
39208
|
+
context,
|
|
39209
|
+
});
|
|
39210
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39211
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39212
|
+
{
|
|
39213
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39214
|
+
riskName &&
|
|
39215
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
|
|
39216
|
+
riskList &&
|
|
39217
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
39218
|
+
actions &&
|
|
39219
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
|
|
39220
|
+
}
|
|
39221
|
+
return await bt.strategyCoreService.getPositionHighestPnlCost(false, symbol, {
|
|
39222
|
+
strategyName: context.strategyName,
|
|
39223
|
+
exchangeName: context.exchangeName,
|
|
39224
|
+
frameName: "",
|
|
39225
|
+
});
|
|
39226
|
+
};
|
|
39227
|
+
/**
|
|
39228
|
+
* Returns whether breakeven was mathematically reachable at the highest profit price.
|
|
39229
|
+
*
|
|
39230
|
+
* @param symbol - Trading pair symbol
|
|
39231
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39232
|
+
* @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
|
|
39233
|
+
*/
|
|
39234
|
+
this.getPositionHighestProfitBreakeven = async (symbol, context) => {
|
|
39235
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
|
|
39236
|
+
symbol,
|
|
39237
|
+
context,
|
|
39238
|
+
});
|
|
39239
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39240
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39241
|
+
{
|
|
39242
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39243
|
+
riskName &&
|
|
39244
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
|
|
39245
|
+
riskList &&
|
|
39246
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
39247
|
+
actions &&
|
|
39248
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
|
|
39249
|
+
}
|
|
39250
|
+
return await bt.strategyCoreService.getPositionHighestProfitBreakeven(false, symbol, {
|
|
39251
|
+
strategyName: context.strategyName,
|
|
39252
|
+
exchangeName: context.exchangeName,
|
|
39253
|
+
frameName: "",
|
|
39254
|
+
});
|
|
39255
|
+
};
|
|
39256
|
+
/**
|
|
39257
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
39258
|
+
*
|
|
39259
|
+
* Measures how long the position has been pulling back from its peak profit level.
|
|
39260
|
+
* Zero when called at the exact moment the peak was set.
|
|
39261
|
+
* Grows continuously as price moves away from the peak without setting a new record.
|
|
39262
|
+
*
|
|
39263
|
+
* Returns null if no pending signal exists.
|
|
39264
|
+
*
|
|
39265
|
+
* @param symbol - Trading pair symbol
|
|
39266
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
39267
|
+
* @returns Drawdown duration in minutes, or null if no active position
|
|
39268
|
+
*/
|
|
39269
|
+
this.getPositionDrawdownMinutes = async (symbol, context) => {
|
|
39270
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
|
|
39271
|
+
symbol,
|
|
39272
|
+
context,
|
|
39273
|
+
});
|
|
39274
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39275
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39276
|
+
{
|
|
39277
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
39278
|
+
riskName &&
|
|
39279
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
|
|
39280
|
+
riskList &&
|
|
39281
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
39282
|
+
actions &&
|
|
39283
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
|
|
39284
|
+
}
|
|
39285
|
+
return await bt.strategyCoreService.getPositionDrawdownMinutes(false, symbol, {
|
|
39286
|
+
strategyName: context.strategyName,
|
|
39287
|
+
exchangeName: context.exchangeName,
|
|
39288
|
+
frameName: "",
|
|
39289
|
+
});
|
|
39290
|
+
};
|
|
37584
39291
|
/**
|
|
37585
39292
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
37586
39293
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38235,7 +39942,7 @@ class LiveUtils {
|
|
|
38235
39942
|
if (!signal) {
|
|
38236
39943
|
return false;
|
|
38237
39944
|
}
|
|
38238
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
39945
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38239
39946
|
strategyName: context.strategyName,
|
|
38240
39947
|
exchangeName: context.exchangeName,
|
|
38241
39948
|
frameName: "",
|
|
@@ -38335,7 +40042,7 @@ class LiveUtils {
|
|
|
38335
40042
|
if (!signal) {
|
|
38336
40043
|
return false;
|
|
38337
40044
|
}
|
|
38338
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40045
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38339
40046
|
strategyName: context.strategyName,
|
|
38340
40047
|
exchangeName: context.exchangeName,
|
|
38341
40048
|
frameName: "",
|
|
@@ -38404,7 +40111,7 @@ class LiveUtils {
|
|
|
38404
40111
|
if (!signal) {
|
|
38405
40112
|
return false;
|
|
38406
40113
|
}
|
|
38407
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40114
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38408
40115
|
strategyName: context.strategyName,
|
|
38409
40116
|
exchangeName: context.exchangeName,
|
|
38410
40117
|
frameName: "",
|
|
@@ -38474,7 +40181,7 @@ class LiveUtils {
|
|
|
38474
40181
|
if (!signal) {
|
|
38475
40182
|
return false;
|
|
38476
40183
|
}
|
|
38477
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40184
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38478
40185
|
strategyName: context.strategyName,
|
|
38479
40186
|
exchangeName: context.exchangeName,
|
|
38480
40187
|
frameName: "",
|
|
@@ -38552,7 +40259,7 @@ class LiveUtils {
|
|
|
38552
40259
|
if (!signal) {
|
|
38553
40260
|
return false;
|
|
38554
40261
|
}
|
|
38555
|
-
const effectivePriceOpen = await bt.strategyCoreService.
|
|
40262
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
|
|
38556
40263
|
strategyName: context.strategyName,
|
|
38557
40264
|
exchangeName: context.exchangeName,
|
|
38558
40265
|
frameName: "",
|
|
@@ -41976,6 +43683,98 @@ class PartialUtils {
|
|
|
41976
43683
|
*/
|
|
41977
43684
|
const Partial = new PartialUtils();
|
|
41978
43685
|
|
|
43686
|
+
const HIGHEST_PROFIT_METHOD_NAME_GET_DATA = "HighestProfitUtils.getData";
|
|
43687
|
+
const HIGHEST_PROFIT_METHOD_NAME_GET_REPORT = "HighestProfitUtils.getReport";
|
|
43688
|
+
const HIGHEST_PROFIT_METHOD_NAME_DUMP = "HighestProfitUtils.dump";
|
|
43689
|
+
/**
|
|
43690
|
+
* Utility class for accessing highest profit reports and statistics.
|
|
43691
|
+
*
|
|
43692
|
+
* Provides static-like methods (via singleton instance) to retrieve data
|
|
43693
|
+
* accumulated by HighestProfitMarkdownService from highestProfitSubject events.
|
|
43694
|
+
*
|
|
43695
|
+
* @example
|
|
43696
|
+
* ```typescript
|
|
43697
|
+
* import { HighestProfit } from "backtest-kit";
|
|
43698
|
+
*
|
|
43699
|
+
* const stats = await HighestProfit.getData("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43700
|
+
* const report = await HighestProfit.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43701
|
+
* await HighestProfit.dump("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
43702
|
+
* ```
|
|
43703
|
+
*/
|
|
43704
|
+
class HighestProfitUtils {
|
|
43705
|
+
constructor() {
|
|
43706
|
+
/**
|
|
43707
|
+
* Retrieves statistical data from accumulated highest profit events.
|
|
43708
|
+
*
|
|
43709
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43710
|
+
* @param context - Execution context
|
|
43711
|
+
* @param backtest - Whether to query backtest data
|
|
43712
|
+
* @returns Promise resolving to HighestProfitStatisticsModel
|
|
43713
|
+
*/
|
|
43714
|
+
this.getData = async (symbol, context, backtest = false) => {
|
|
43715
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
43716
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43717
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43718
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43719
|
+
{
|
|
43720
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43721
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
|
|
43722
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
|
|
43723
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
|
|
43724
|
+
}
|
|
43725
|
+
return await bt.highestProfitMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
43726
|
+
};
|
|
43727
|
+
/**
|
|
43728
|
+
* Generates a markdown report with all highest profit events for a symbol-strategy pair.
|
|
43729
|
+
*
|
|
43730
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43731
|
+
* @param context - Execution context
|
|
43732
|
+
* @param backtest - Whether to query backtest data
|
|
43733
|
+
* @param columns - Optional column configuration
|
|
43734
|
+
* @returns Promise resolving to markdown formatted report string
|
|
43735
|
+
*/
|
|
43736
|
+
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
43737
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
43738
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43739
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43740
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43741
|
+
{
|
|
43742
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43743
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
|
|
43744
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
|
|
43745
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
|
|
43746
|
+
}
|
|
43747
|
+
return await bt.highestProfitMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
|
|
43748
|
+
};
|
|
43749
|
+
/**
|
|
43750
|
+
* Generates and saves a markdown report to file.
|
|
43751
|
+
*
|
|
43752
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
43753
|
+
* @param context - Execution context
|
|
43754
|
+
* @param backtest - Whether to query backtest data
|
|
43755
|
+
* @param path - Output directory path (default: "./dump/highest_profit")
|
|
43756
|
+
* @param columns - Optional column configuration
|
|
43757
|
+
*/
|
|
43758
|
+
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
43759
|
+
bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
43760
|
+
bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43761
|
+
bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43762
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43763
|
+
{
|
|
43764
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
43765
|
+
riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
|
|
43766
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
|
|
43767
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
|
|
43768
|
+
}
|
|
43769
|
+
await bt.highestProfitMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
|
|
43770
|
+
};
|
|
43771
|
+
}
|
|
43772
|
+
}
|
|
43773
|
+
/**
|
|
43774
|
+
* Global singleton instance of HighestProfitUtils.
|
|
43775
|
+
*/
|
|
43776
|
+
const HighestProfit = new HighestProfitUtils();
|
|
43777
|
+
|
|
41979
43778
|
/**
|
|
41980
43779
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
41981
43780
|
*
|
|
@@ -46453,6 +48252,7 @@ exports.Constant = Constant;
|
|
|
46453
48252
|
exports.Exchange = Exchange;
|
|
46454
48253
|
exports.ExecutionContextService = ExecutionContextService;
|
|
46455
48254
|
exports.Heat = Heat;
|
|
48255
|
+
exports.HighestProfit = HighestProfit;
|
|
46456
48256
|
exports.Live = Live;
|
|
46457
48257
|
exports.Log = Log;
|
|
46458
48258
|
exports.Markdown = Markdown;
|
|
@@ -46532,8 +48332,17 @@ exports.getMode = getMode;
|
|
|
46532
48332
|
exports.getNextCandles = getNextCandles;
|
|
46533
48333
|
exports.getOrderBook = getOrderBook;
|
|
46534
48334
|
exports.getPendingSignal = getPendingSignal;
|
|
46535
|
-
exports.
|
|
48335
|
+
exports.getPositionCountdownMinutes = getPositionCountdownMinutes;
|
|
48336
|
+
exports.getPositionDrawdownMinutes = getPositionDrawdownMinutes;
|
|
48337
|
+
exports.getPositionEffectivePrice = getPositionEffectivePrice;
|
|
48338
|
+
exports.getPositionEntries = getPositionEntries;
|
|
46536
48339
|
exports.getPositionEntryOverlap = getPositionEntryOverlap;
|
|
48340
|
+
exports.getPositionEstimateMinutes = getPositionEstimateMinutes;
|
|
48341
|
+
exports.getPositionHighestPnlCost = getPositionHighestPnlCost;
|
|
48342
|
+
exports.getPositionHighestPnlPercentage = getPositionHighestPnlPercentage;
|
|
48343
|
+
exports.getPositionHighestProfitBreakeven = getPositionHighestProfitBreakeven;
|
|
48344
|
+
exports.getPositionHighestProfitPrice = getPositionHighestProfitPrice;
|
|
48345
|
+
exports.getPositionHighestProfitTimestamp = getPositionHighestProfitTimestamp;
|
|
46537
48346
|
exports.getPositionInvestedCost = getPositionInvestedCost;
|
|
46538
48347
|
exports.getPositionInvestedCount = getPositionInvestedCount;
|
|
46539
48348
|
exports.getPositionLevels = getPositionLevels;
|
|
@@ -46574,6 +48383,8 @@ exports.listenDoneWalker = listenDoneWalker;
|
|
|
46574
48383
|
exports.listenDoneWalkerOnce = listenDoneWalkerOnce;
|
|
46575
48384
|
exports.listenError = listenError;
|
|
46576
48385
|
exports.listenExit = listenExit;
|
|
48386
|
+
exports.listenHighestProfit = listenHighestProfit;
|
|
48387
|
+
exports.listenHighestProfitOnce = listenHighestProfitOnce;
|
|
46577
48388
|
exports.listenPartialLossAvailable = listenPartialLossAvailable;
|
|
46578
48389
|
exports.listenPartialLossAvailableOnce = listenPartialLossAvailableOnce;
|
|
46579
48390
|
exports.listenPartialProfitAvailable = listenPartialProfitAvailable;
|