backtest-kit 6.5.2 → 6.7.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 +1752 -111
- package/build/index.mjs +1744 -112
- package/package.json +1 -1
- package/types.d.ts +951 -98
package/build/index.mjs
CHANGED
|
@@ -108,6 +108,7 @@ const markdownServices$1 = {
|
|
|
108
108
|
strategyMarkdownService: Symbol('strategyMarkdownService'),
|
|
109
109
|
syncMarkdownService: Symbol('syncMarkdownService'),
|
|
110
110
|
highestProfitMarkdownService: Symbol('highestProfitMarkdownService'),
|
|
111
|
+
maxDrawdownMarkdownService: Symbol('maxDrawdownMarkdownService'),
|
|
111
112
|
};
|
|
112
113
|
const reportServices$1 = {
|
|
113
114
|
backtestReportService: Symbol('backtestReportService'),
|
|
@@ -122,6 +123,7 @@ const reportServices$1 = {
|
|
|
122
123
|
strategyReportService: Symbol('strategyReportService'),
|
|
123
124
|
syncReportService: Symbol('syncReportService'),
|
|
124
125
|
highestProfitReportService: Symbol('highestProfitReportService'),
|
|
126
|
+
maxDrawdownReportService: Symbol('maxDrawdownReportService'),
|
|
125
127
|
};
|
|
126
128
|
const validationServices$1 = {
|
|
127
129
|
exchangeValidationService: Symbol('exchangeValidationService'),
|
|
@@ -453,6 +455,13 @@ const GLOBAL_CONFIG = {
|
|
|
453
455
|
* Default: 250 events
|
|
454
456
|
*/
|
|
455
457
|
CC_MAX_HIGHEST_PROFIT_MARKDOWN_ROWS: 250,
|
|
458
|
+
/**
|
|
459
|
+
* Maximum number of events to keep in max drawdown markdown report storage.
|
|
460
|
+
* Older events are removed (FIFO) when this limit is exceeded.
|
|
461
|
+
*
|
|
462
|
+
* Default: 250 events
|
|
463
|
+
*/
|
|
464
|
+
CC_MAX_MAX_DRAWDOWN_MARKDOWN_ROWS: 250,
|
|
456
465
|
/**
|
|
457
466
|
* Maximum number of events to keep in live markdown report storage.
|
|
458
467
|
* Older events are removed (FIFO) when this limit is exceeded.
|
|
@@ -727,6 +736,12 @@ const backtestScheduleOpenSubject = new Subject();
|
|
|
727
736
|
* Allows users to track profit milestones and implement custom management logic based on profit levels.
|
|
728
737
|
*/
|
|
729
738
|
const highestProfitSubject = new Subject();
|
|
739
|
+
/**
|
|
740
|
+
* Max drawdown emitter for real-time risk tracking.
|
|
741
|
+
* Emits updates on the maximum drawdown experienced for an open position.
|
|
742
|
+
* Allows users to track drawdown levels and implement custom risk management logic based on drawdown thresholds.
|
|
743
|
+
*/
|
|
744
|
+
const maxDrawdownSubject = new Subject();
|
|
730
745
|
|
|
731
746
|
var emitters = /*#__PURE__*/Object.freeze({
|
|
732
747
|
__proto__: null,
|
|
@@ -739,6 +754,7 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
739
754
|
errorEmitter: errorEmitter,
|
|
740
755
|
exitEmitter: exitEmitter,
|
|
741
756
|
highestProfitSubject: highestProfitSubject,
|
|
757
|
+
maxDrawdownSubject: maxDrawdownSubject,
|
|
742
758
|
partialLossSubject: partialLossSubject,
|
|
743
759
|
partialProfitSubject: partialProfitSubject,
|
|
744
760
|
performanceEmitter: performanceEmitter,
|
|
@@ -4868,6 +4884,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4868
4884
|
_isScheduled: false,
|
|
4869
4885
|
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4870
4886
|
_peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4887
|
+
_fall: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4871
4888
|
};
|
|
4872
4889
|
// Валидируем сигнал перед возвратом
|
|
4873
4890
|
validatePendingSignal(signalRow, currentPrice);
|
|
@@ -4893,6 +4910,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4893
4910
|
_isScheduled: true,
|
|
4894
4911
|
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4895
4912
|
_peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4913
|
+
_fall: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4896
4914
|
};
|
|
4897
4915
|
// Валидируем сигнал перед возвратом
|
|
4898
4916
|
validateScheduledSignal(scheduledSignalRow, currentPrice);
|
|
@@ -4915,6 +4933,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
4915
4933
|
_isScheduled: false,
|
|
4916
4934
|
_entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4917
4935
|
_peak: { price: currentPrice, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4936
|
+
_fall: { price: currentPrice, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
4918
4937
|
};
|
|
4919
4938
|
// Валидируем сигнал перед возвратом
|
|
4920
4939
|
validatePendingSignal(signalRow, currentPrice);
|
|
@@ -5626,6 +5645,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
5626
5645
|
pendingAt: activationTime,
|
|
5627
5646
|
_isScheduled: false,
|
|
5628
5647
|
_peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
5648
|
+
_fall: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
5629
5649
|
};
|
|
5630
5650
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
5631
5651
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
|
|
@@ -6300,6 +6320,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backt
|
|
|
6300
6320
|
const slDistance = effectivePriceOpen - effectiveStopLoss;
|
|
6301
6321
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
6302
6322
|
percentSl = Math.min(progressPercent, 100);
|
|
6323
|
+
if (currentPrice < signal._fall.price) {
|
|
6324
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
|
|
6325
|
+
signal._fall = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6326
|
+
if (self.params.callbacks?.onWrite) {
|
|
6327
|
+
self.params.callbacks.onWrite(signal.symbol, signal, backtest);
|
|
6328
|
+
}
|
|
6329
|
+
!backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
6330
|
+
await self.params.onMaxDrawdown(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
|
|
6331
|
+
}
|
|
6303
6332
|
await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
|
|
6304
6333
|
}
|
|
6305
6334
|
}
|
|
@@ -6333,6 +6362,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backt
|
|
|
6333
6362
|
const slDistance = effectiveStopLoss - effectivePriceOpen;
|
|
6334
6363
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
6335
6364
|
percentSl = Math.min(progressPercent, 100);
|
|
6365
|
+
if (currentPrice > signal._fall.price) {
|
|
6366
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
|
|
6367
|
+
signal._fall = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6368
|
+
if (self.params.callbacks?.onWrite) {
|
|
6369
|
+
self.params.callbacks.onWrite(signal.symbol, signal, backtest);
|
|
6370
|
+
}
|
|
6371
|
+
!backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
|
|
6372
|
+
await self.params.onMaxDrawdown(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
|
|
6373
|
+
}
|
|
6336
6374
|
await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
|
|
6337
6375
|
}
|
|
6338
6376
|
}
|
|
@@ -6453,6 +6491,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
6453
6491
|
pendingAt: activationTime,
|
|
6454
6492
|
_isScheduled: false,
|
|
6455
6493
|
_peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
6494
|
+
_fall: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
|
|
6456
6495
|
};
|
|
6457
6496
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
6458
6497
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
|
|
@@ -6639,6 +6678,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
|
|
|
6639
6678
|
pendingAt: candle.timestamp,
|
|
6640
6679
|
_isScheduled: false,
|
|
6641
6680
|
_peak: { price: activatedSignal.priceOpen, timestamp: candle.timestamp, pnlPercentage: 0, pnlCost: 0 },
|
|
6681
|
+
_fall: { price: activatedSignal.priceOpen, timestamp: candle.timestamp, pnlPercentage: 0, pnlCost: 0 },
|
|
6642
6682
|
};
|
|
6643
6683
|
// Sync open: if external system rejects — cancel scheduled signal instead of opening
|
|
6644
6684
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(candle.timestamp, pendingSignal.priceOpen, pendingSignal, self);
|
|
@@ -6863,6 +6903,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
6863
6903
|
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
6864
6904
|
const slDistance = effectivePriceOpen - effectiveStopLoss;
|
|
6865
6905
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
6906
|
+
if (averagePrice < signal._fall.price) {
|
|
6907
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
|
|
6908
|
+
signal._fall = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6909
|
+
if (self.params.callbacks?.onWrite) {
|
|
6910
|
+
self.params.callbacks.onWrite(signal.symbol, signal, true);
|
|
6911
|
+
}
|
|
6912
|
+
await self.params.onMaxDrawdown(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
|
|
6913
|
+
}
|
|
6866
6914
|
await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
6867
6915
|
}
|
|
6868
6916
|
}
|
|
@@ -6893,6 +6941,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles, frameEnd
|
|
|
6893
6941
|
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
6894
6942
|
const slDistance = effectiveStopLoss - effectivePriceOpen;
|
|
6895
6943
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
6944
|
+
if (averagePrice > signal._fall.price) {
|
|
6945
|
+
const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
|
|
6946
|
+
signal._fall = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
|
|
6947
|
+
if (self.params.callbacks?.onWrite) {
|
|
6948
|
+
self.params.callbacks.onWrite(signal.symbol, signal, true);
|
|
6949
|
+
}
|
|
6950
|
+
await self.params.onMaxDrawdown(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
|
|
6951
|
+
}
|
|
6896
6952
|
await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
6897
6953
|
}
|
|
6898
6954
|
}
|
|
@@ -7614,6 +7670,104 @@ class ClientStrategy {
|
|
|
7614
7670
|
}
|
|
7615
7671
|
return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
|
|
7616
7672
|
}
|
|
7673
|
+
/**
|
|
7674
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
7675
|
+
*
|
|
7676
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
7677
|
+
* pulling back from its peak profit level.
|
|
7678
|
+
*
|
|
7679
|
+
* Returns null if no pending signal exists.
|
|
7680
|
+
*
|
|
7681
|
+
* @param symbol - Trading pair symbol
|
|
7682
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
7683
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
7684
|
+
*/
|
|
7685
|
+
async getPositionHighestProfitMinutes(symbol, timestamp) {
|
|
7686
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitMinutes", { symbol });
|
|
7687
|
+
if (!this._pendingSignal) {
|
|
7688
|
+
return null;
|
|
7689
|
+
}
|
|
7690
|
+
return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
|
|
7691
|
+
}
|
|
7692
|
+
/**
|
|
7693
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
7694
|
+
*
|
|
7695
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
7696
|
+
* Zero when called at the exact moment the trough was set.
|
|
7697
|
+
*
|
|
7698
|
+
* Returns null if no pending signal exists.
|
|
7699
|
+
*
|
|
7700
|
+
* @param symbol - Trading pair symbol
|
|
7701
|
+
* @param timestamp - Current Unix timestamp in milliseconds
|
|
7702
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
7703
|
+
*/
|
|
7704
|
+
async getPositionMaxDrawdownMinutes(symbol, timestamp) {
|
|
7705
|
+
this.params.logger.debug("ClientStrategy getPositionMaxDrawdownMinutes", { symbol });
|
|
7706
|
+
if (!this._pendingSignal) {
|
|
7707
|
+
return null;
|
|
7708
|
+
}
|
|
7709
|
+
return Math.floor((timestamp - this._pendingSignal._fall.timestamp) / 60000);
|
|
7710
|
+
}
|
|
7711
|
+
/**
|
|
7712
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
7713
|
+
*
|
|
7714
|
+
* Returns null if no pending signal exists.
|
|
7715
|
+
*
|
|
7716
|
+
* @param symbol - Trading pair symbol
|
|
7717
|
+
* @returns Promise resolving to price or null
|
|
7718
|
+
*/
|
|
7719
|
+
async getPositionMaxDrawdownPrice(symbol) {
|
|
7720
|
+
this.params.logger.debug("ClientStrategy getPositionMaxDrawdownPrice", { symbol });
|
|
7721
|
+
if (!this._pendingSignal) {
|
|
7722
|
+
return null;
|
|
7723
|
+
}
|
|
7724
|
+
return this._pendingSignal._fall.price;
|
|
7725
|
+
}
|
|
7726
|
+
/**
|
|
7727
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
7728
|
+
*
|
|
7729
|
+
* Returns null if no pending signal exists.
|
|
7730
|
+
*
|
|
7731
|
+
* @param symbol - Trading pair symbol
|
|
7732
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
7733
|
+
*/
|
|
7734
|
+
async getPositionMaxDrawdownTimestamp(symbol) {
|
|
7735
|
+
this.params.logger.debug("ClientStrategy getPositionMaxDrawdownTimestamp", { symbol });
|
|
7736
|
+
if (!this._pendingSignal) {
|
|
7737
|
+
return null;
|
|
7738
|
+
}
|
|
7739
|
+
return this._pendingSignal._fall.timestamp;
|
|
7740
|
+
}
|
|
7741
|
+
/**
|
|
7742
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
7743
|
+
*
|
|
7744
|
+
* Returns null if no pending signal exists.
|
|
7745
|
+
*
|
|
7746
|
+
* @param symbol - Trading pair symbol
|
|
7747
|
+
* @returns Promise resolving to PnL percentage or null
|
|
7748
|
+
*/
|
|
7749
|
+
async getPositionMaxDrawdownPnlPercentage(symbol) {
|
|
7750
|
+
this.params.logger.debug("ClientStrategy getPositionMaxDrawdownPnlPercentage", { symbol });
|
|
7751
|
+
if (!this._pendingSignal) {
|
|
7752
|
+
return null;
|
|
7753
|
+
}
|
|
7754
|
+
return this._pendingSignal._fall.pnlPercentage;
|
|
7755
|
+
}
|
|
7756
|
+
/**
|
|
7757
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
7758
|
+
*
|
|
7759
|
+
* Returns null if no pending signal exists.
|
|
7760
|
+
*
|
|
7761
|
+
* @param symbol - Trading pair symbol
|
|
7762
|
+
* @returns Promise resolving to PnL cost or null
|
|
7763
|
+
*/
|
|
7764
|
+
async getPositionMaxDrawdownPnlCost(symbol) {
|
|
7765
|
+
this.params.logger.debug("ClientStrategy getPositionMaxDrawdownPnlCost", { symbol });
|
|
7766
|
+
if (!this._pendingSignal) {
|
|
7767
|
+
return null;
|
|
7768
|
+
}
|
|
7769
|
+
return this._pendingSignal._fall.pnlCost;
|
|
7770
|
+
}
|
|
7617
7771
|
/**
|
|
7618
7772
|
* Performs a single tick of strategy execution.
|
|
7619
7773
|
*
|
|
@@ -7790,6 +7944,7 @@ class ClientStrategy {
|
|
|
7790
7944
|
pendingAt: currentTime,
|
|
7791
7945
|
_isScheduled: false,
|
|
7792
7946
|
_peak: { price: activatedSignal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
7947
|
+
_fall: { price: activatedSignal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
|
|
7793
7948
|
};
|
|
7794
7949
|
const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(currentTime, currentPrice, pendingSignal, this);
|
|
7795
7950
|
if (!syncOpenAllowed) {
|
|
@@ -9906,7 +10061,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
9906
10061
|
* @param backtest - Whether running in backtest mode
|
|
9907
10062
|
* @returns Unique string key for memoization
|
|
9908
10063
|
*/
|
|
9909
|
-
const CREATE_KEY_FN$
|
|
10064
|
+
const CREATE_KEY_FN$s = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9910
10065
|
const parts = [symbol, strategyName, exchangeName];
|
|
9911
10066
|
if (frameName)
|
|
9912
10067
|
parts.push(frameName);
|
|
@@ -10067,6 +10222,42 @@ const CREATE_HIGHEST_PROFIT_FN = (self, strategyName, exchangeName, frameName, i
|
|
|
10067
10222
|
},
|
|
10068
10223
|
defaultValue: null,
|
|
10069
10224
|
});
|
|
10225
|
+
/**
|
|
10226
|
+
* Creates a callback function for emitting max drawdown updates to maxDrawdownSubject.
|
|
10227
|
+
* Called by ClientStrategy when the maximum drawdown for an open position is updated.
|
|
10228
|
+
* Emits MaxDrawdownContract event to all subscribers with the current price and timestamp.
|
|
10229
|
+
* Used for real-time risk tracking and management logic based on drawdown levels.
|
|
10230
|
+
* @param self - Reference to StrategyConnectionService instance
|
|
10231
|
+
* @param strategyName - Name of the strategy
|
|
10232
|
+
* @param exchangeName - Name of the exchange
|
|
10233
|
+
* @param frameName - Name of the frame
|
|
10234
|
+
* @param isBacktest - Flag indicating if the operation is for backtesting
|
|
10235
|
+
* @return Callback function for max drawdown updates
|
|
10236
|
+
*/
|
|
10237
|
+
const CREATE_MAX_DRAWDOWN_FN = (self, strategyName, exchangeName, frameName, isBacktest) => trycatch(async (signal, currentPrice, timestamp) => {
|
|
10238
|
+
await maxDrawdownSubject.next({
|
|
10239
|
+
symbol: signal.symbol,
|
|
10240
|
+
signal,
|
|
10241
|
+
currentPrice,
|
|
10242
|
+
timestamp,
|
|
10243
|
+
strategyName,
|
|
10244
|
+
exchangeName,
|
|
10245
|
+
frameName,
|
|
10246
|
+
backtest: isBacktest,
|
|
10247
|
+
});
|
|
10248
|
+
}, {
|
|
10249
|
+
fallback: (error) => {
|
|
10250
|
+
const message = "StrategyConnectionService CREATE_MAX_DRAWDOWN_FN thrown";
|
|
10251
|
+
const payload = {
|
|
10252
|
+
error: errorData(error),
|
|
10253
|
+
message: getErrorMessage(error),
|
|
10254
|
+
};
|
|
10255
|
+
bt.loggerService.warn(message, payload);
|
|
10256
|
+
console.warn(message, payload);
|
|
10257
|
+
errorEmitter.next(error);
|
|
10258
|
+
},
|
|
10259
|
+
defaultValue: null,
|
|
10260
|
+
});
|
|
10070
10261
|
/**
|
|
10071
10262
|
* Creates a callback function for emitting dispose events.
|
|
10072
10263
|
*
|
|
@@ -10137,7 +10328,7 @@ class StrategyConnectionService {
|
|
|
10137
10328
|
* @param backtest - Whether running in backtest mode
|
|
10138
10329
|
* @returns Configured ClientStrategy instance
|
|
10139
10330
|
*/
|
|
10140
|
-
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10331
|
+
this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
10141
10332
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
10142
10333
|
return new ClientStrategy({
|
|
10143
10334
|
symbol,
|
|
@@ -10166,6 +10357,7 @@ class StrategyConnectionService {
|
|
|
10166
10357
|
onCommit: CREATE_COMMIT_FN(),
|
|
10167
10358
|
onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
10168
10359
|
onHighestProfit: CREATE_HIGHEST_PROFIT_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
10360
|
+
onMaxDrawdown: CREATE_MAX_DRAWDOWN_FN(this, strategyName, exchangeName, frameName, backtest),
|
|
10169
10361
|
});
|
|
10170
10362
|
});
|
|
10171
10363
|
/**
|
|
@@ -10769,6 +10961,130 @@ class StrategyConnectionService {
|
|
|
10769
10961
|
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
10770
10962
|
return await strategy.getPositionDrawdownMinutes(symbol, timestamp);
|
|
10771
10963
|
};
|
|
10964
|
+
/**
|
|
10965
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
10966
|
+
*
|
|
10967
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
10968
|
+
* pulling back from its peak profit level.
|
|
10969
|
+
*
|
|
10970
|
+
* Resolves current timestamp via timeMetaService and delegates to
|
|
10971
|
+
* ClientStrategy.getPositionHighestProfitMinutes().
|
|
10972
|
+
* Returns null if no pending signal exists.
|
|
10973
|
+
*
|
|
10974
|
+
* @param backtest - Whether running in backtest mode
|
|
10975
|
+
* @param symbol - Trading pair symbol
|
|
10976
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
10977
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
10978
|
+
*/
|
|
10979
|
+
this.getPositionHighestProfitMinutes = async (backtest, symbol, context) => {
|
|
10980
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitMinutes", {
|
|
10981
|
+
symbol,
|
|
10982
|
+
context,
|
|
10983
|
+
});
|
|
10984
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
10985
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
10986
|
+
return await strategy.getPositionHighestProfitMinutes(symbol, timestamp);
|
|
10987
|
+
};
|
|
10988
|
+
/**
|
|
10989
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
10990
|
+
*
|
|
10991
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
10992
|
+
* Zero when called at the exact moment the trough was set.
|
|
10993
|
+
*
|
|
10994
|
+
* Resolves current timestamp via timeMetaService and delegates to
|
|
10995
|
+
* ClientStrategy.getPositionMaxDrawdownMinutes().
|
|
10996
|
+
* Returns null if no pending signal exists.
|
|
10997
|
+
*
|
|
10998
|
+
* @param backtest - Whether running in backtest mode
|
|
10999
|
+
* @param symbol - Trading pair symbol
|
|
11000
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11001
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
11002
|
+
*/
|
|
11003
|
+
this.getPositionMaxDrawdownMinutes = async (backtest, symbol, context) => {
|
|
11004
|
+
this.loggerService.log("strategyConnectionService getPositionMaxDrawdownMinutes", {
|
|
11005
|
+
symbol,
|
|
11006
|
+
context,
|
|
11007
|
+
});
|
|
11008
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11009
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
11010
|
+
return await strategy.getPositionMaxDrawdownMinutes(symbol, timestamp);
|
|
11011
|
+
};
|
|
11012
|
+
/**
|
|
11013
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
11014
|
+
*
|
|
11015
|
+
* Delegates to ClientStrategy.getPositionMaxDrawdownPrice().
|
|
11016
|
+
* Returns null if no pending signal exists.
|
|
11017
|
+
*
|
|
11018
|
+
* @param backtest - Whether running in backtest mode
|
|
11019
|
+
* @param symbol - Trading pair symbol
|
|
11020
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11021
|
+
* @returns Promise resolving to price or null
|
|
11022
|
+
*/
|
|
11023
|
+
this.getPositionMaxDrawdownPrice = async (backtest, symbol, context) => {
|
|
11024
|
+
this.loggerService.log("strategyConnectionService getPositionMaxDrawdownPrice", {
|
|
11025
|
+
symbol,
|
|
11026
|
+
context,
|
|
11027
|
+
});
|
|
11028
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11029
|
+
return await strategy.getPositionMaxDrawdownPrice(symbol);
|
|
11030
|
+
};
|
|
11031
|
+
/**
|
|
11032
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
11033
|
+
*
|
|
11034
|
+
* Delegates to ClientStrategy.getPositionMaxDrawdownTimestamp().
|
|
11035
|
+
* Returns null if no pending signal exists.
|
|
11036
|
+
*
|
|
11037
|
+
* @param backtest - Whether running in backtest mode
|
|
11038
|
+
* @param symbol - Trading pair symbol
|
|
11039
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11040
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
11041
|
+
*/
|
|
11042
|
+
this.getPositionMaxDrawdownTimestamp = async (backtest, symbol, context) => {
|
|
11043
|
+
this.loggerService.log("strategyConnectionService getPositionMaxDrawdownTimestamp", {
|
|
11044
|
+
symbol,
|
|
11045
|
+
context,
|
|
11046
|
+
});
|
|
11047
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11048
|
+
return await strategy.getPositionMaxDrawdownTimestamp(symbol);
|
|
11049
|
+
};
|
|
11050
|
+
/**
|
|
11051
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
11052
|
+
*
|
|
11053
|
+
* Delegates to ClientStrategy.getPositionMaxDrawdownPnlPercentage().
|
|
11054
|
+
* Returns null if no pending signal exists.
|
|
11055
|
+
*
|
|
11056
|
+
* @param backtest - Whether running in backtest mode
|
|
11057
|
+
* @param symbol - Trading pair symbol
|
|
11058
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11059
|
+
* @returns Promise resolving to PnL percentage or null
|
|
11060
|
+
*/
|
|
11061
|
+
this.getPositionMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
11062
|
+
this.loggerService.log("strategyConnectionService getPositionMaxDrawdownPnlPercentage", {
|
|
11063
|
+
symbol,
|
|
11064
|
+
context,
|
|
11065
|
+
});
|
|
11066
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11067
|
+
return await strategy.getPositionMaxDrawdownPnlPercentage(symbol);
|
|
11068
|
+
};
|
|
11069
|
+
/**
|
|
11070
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
11071
|
+
*
|
|
11072
|
+
* Delegates to ClientStrategy.getPositionMaxDrawdownPnlCost().
|
|
11073
|
+
* Returns null if no pending signal exists.
|
|
11074
|
+
*
|
|
11075
|
+
* @param backtest - Whether running in backtest mode
|
|
11076
|
+
* @param symbol - Trading pair symbol
|
|
11077
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11078
|
+
* @returns Promise resolving to PnL cost or null
|
|
11079
|
+
*/
|
|
11080
|
+
this.getPositionMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
11081
|
+
this.loggerService.log("strategyConnectionService getPositionMaxDrawdownPnlCost", {
|
|
11082
|
+
symbol,
|
|
11083
|
+
context,
|
|
11084
|
+
});
|
|
11085
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11086
|
+
return await strategy.getPositionMaxDrawdownPnlCost(symbol);
|
|
11087
|
+
};
|
|
10772
11088
|
/**
|
|
10773
11089
|
* Disposes the ClientStrategy instance for the given context.
|
|
10774
11090
|
*
|
|
@@ -10807,7 +11123,7 @@ class StrategyConnectionService {
|
|
|
10807
11123
|
}
|
|
10808
11124
|
return;
|
|
10809
11125
|
}
|
|
10810
|
-
const key = CREATE_KEY_FN$
|
|
11126
|
+
const key = CREATE_KEY_FN$s(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
10811
11127
|
if (!this.getStrategy.has(key)) {
|
|
10812
11128
|
return;
|
|
10813
11129
|
}
|
|
@@ -11983,7 +12299,7 @@ class ClientRisk {
|
|
|
11983
12299
|
* @param backtest - Whether running in backtest mode
|
|
11984
12300
|
* @returns Unique string key for memoization
|
|
11985
12301
|
*/
|
|
11986
|
-
const CREATE_KEY_FN$
|
|
12302
|
+
const CREATE_KEY_FN$r = (riskName, exchangeName, frameName, backtest) => {
|
|
11987
12303
|
const parts = [riskName, exchangeName];
|
|
11988
12304
|
if (frameName)
|
|
11989
12305
|
parts.push(frameName);
|
|
@@ -12082,7 +12398,7 @@ class RiskConnectionService {
|
|
|
12082
12398
|
* @param backtest - True if backtest mode, false if live mode
|
|
12083
12399
|
* @returns Configured ClientRisk instance
|
|
12084
12400
|
*/
|
|
12085
|
-
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12401
|
+
this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
12086
12402
|
const schema = this.riskSchemaService.get(riskName);
|
|
12087
12403
|
return new ClientRisk({
|
|
12088
12404
|
...schema,
|
|
@@ -12150,7 +12466,7 @@ class RiskConnectionService {
|
|
|
12150
12466
|
payload,
|
|
12151
12467
|
});
|
|
12152
12468
|
if (payload) {
|
|
12153
|
-
const key = CREATE_KEY_FN$
|
|
12469
|
+
const key = CREATE_KEY_FN$r(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12154
12470
|
this.getRisk.clear(key);
|
|
12155
12471
|
}
|
|
12156
12472
|
else {
|
|
@@ -13657,7 +13973,7 @@ class ClientAction {
|
|
|
13657
13973
|
* @param backtest - Whether running in backtest mode
|
|
13658
13974
|
* @returns Unique string key for memoization
|
|
13659
13975
|
*/
|
|
13660
|
-
const CREATE_KEY_FN$
|
|
13976
|
+
const CREATE_KEY_FN$q = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13661
13977
|
const parts = [actionName, strategyName, exchangeName];
|
|
13662
13978
|
if (frameName)
|
|
13663
13979
|
parts.push(frameName);
|
|
@@ -13708,7 +14024,7 @@ class ActionConnectionService {
|
|
|
13708
14024
|
* @param backtest - True if backtest mode, false if live mode
|
|
13709
14025
|
* @returns Configured ClientAction instance
|
|
13710
14026
|
*/
|
|
13711
|
-
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
14027
|
+
this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$q(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
13712
14028
|
const schema = this.actionSchemaService.get(actionName);
|
|
13713
14029
|
return new ClientAction({
|
|
13714
14030
|
...schema,
|
|
@@ -13918,7 +14234,7 @@ class ActionConnectionService {
|
|
|
13918
14234
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
13919
14235
|
return;
|
|
13920
14236
|
}
|
|
13921
|
-
const key = CREATE_KEY_FN$
|
|
14237
|
+
const key = CREATE_KEY_FN$q(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
13922
14238
|
if (!this.getAction.has(key)) {
|
|
13923
14239
|
return;
|
|
13924
14240
|
}
|
|
@@ -13936,7 +14252,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
13936
14252
|
* @param exchangeName - Exchange name
|
|
13937
14253
|
* @returns Unique string key for memoization
|
|
13938
14254
|
*/
|
|
13939
|
-
const CREATE_KEY_FN$
|
|
14255
|
+
const CREATE_KEY_FN$p = (exchangeName) => {
|
|
13940
14256
|
return exchangeName;
|
|
13941
14257
|
};
|
|
13942
14258
|
/**
|
|
@@ -13960,7 +14276,7 @@ class ExchangeCoreService {
|
|
|
13960
14276
|
* @param exchangeName - Name of the exchange to validate
|
|
13961
14277
|
* @returns Promise that resolves when validation is complete
|
|
13962
14278
|
*/
|
|
13963
|
-
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
14279
|
+
this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$p(exchangeName), async (exchangeName) => {
|
|
13964
14280
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
13965
14281
|
exchangeName,
|
|
13966
14282
|
});
|
|
@@ -14212,7 +14528,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
14212
14528
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14213
14529
|
* @returns Unique string key for memoization
|
|
14214
14530
|
*/
|
|
14215
|
-
const CREATE_KEY_FN$
|
|
14531
|
+
const CREATE_KEY_FN$o = (context) => {
|
|
14216
14532
|
const parts = [context.strategyName, context.exchangeName];
|
|
14217
14533
|
if (context.frameName)
|
|
14218
14534
|
parts.push(context.frameName);
|
|
@@ -14244,7 +14560,7 @@ class StrategyCoreService {
|
|
|
14244
14560
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
14245
14561
|
* @returns Promise that resolves when validation is complete
|
|
14246
14562
|
*/
|
|
14247
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
14563
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
|
|
14248
14564
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
14249
14565
|
context,
|
|
14250
14566
|
});
|
|
@@ -15288,6 +15604,114 @@ class StrategyCoreService {
|
|
|
15288
15604
|
await this.validate(context);
|
|
15289
15605
|
return await this.strategyConnectionService.getPositionDrawdownMinutes(backtest, symbol, context);
|
|
15290
15606
|
};
|
|
15607
|
+
/**
|
|
15608
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
15609
|
+
*
|
|
15610
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
15611
|
+
* pulling back from its peak profit level.
|
|
15612
|
+
*
|
|
15613
|
+
* Validates strategy existence and delegates to connection service.
|
|
15614
|
+
* Returns null if no pending signal exists.
|
|
15615
|
+
*
|
|
15616
|
+
* @param backtest - Whether running in backtest mode
|
|
15617
|
+
* @param symbol - Trading pair symbol
|
|
15618
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15619
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
15620
|
+
*/
|
|
15621
|
+
this.getPositionHighestProfitMinutes = async (backtest, symbol, context) => {
|
|
15622
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitMinutes", {
|
|
15623
|
+
symbol,
|
|
15624
|
+
context,
|
|
15625
|
+
});
|
|
15626
|
+
await this.validate(context);
|
|
15627
|
+
return await this.strategyConnectionService.getPositionHighestProfitMinutes(backtest, symbol, context);
|
|
15628
|
+
};
|
|
15629
|
+
/**
|
|
15630
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
15631
|
+
*
|
|
15632
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
15633
|
+
* Zero when called at the exact moment the trough was set.
|
|
15634
|
+
*
|
|
15635
|
+
* Validates strategy existence and delegates to connection service.
|
|
15636
|
+
* Returns null if no pending signal exists.
|
|
15637
|
+
*
|
|
15638
|
+
* @param backtest - Whether running in backtest mode
|
|
15639
|
+
* @param symbol - Trading pair symbol
|
|
15640
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15641
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
15642
|
+
*/
|
|
15643
|
+
this.getPositionMaxDrawdownMinutes = async (backtest, symbol, context) => {
|
|
15644
|
+
this.loggerService.log("strategyCoreService getPositionMaxDrawdownMinutes", {
|
|
15645
|
+
symbol,
|
|
15646
|
+
context,
|
|
15647
|
+
});
|
|
15648
|
+
await this.validate(context);
|
|
15649
|
+
return await this.strategyConnectionService.getPositionMaxDrawdownMinutes(backtest, symbol, context);
|
|
15650
|
+
};
|
|
15651
|
+
/**
|
|
15652
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
15653
|
+
*
|
|
15654
|
+
* @param backtest - Whether running in backtest mode
|
|
15655
|
+
* @param symbol - Trading pair symbol
|
|
15656
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15657
|
+
* @returns Promise resolving to price or null
|
|
15658
|
+
*/
|
|
15659
|
+
this.getPositionMaxDrawdownPrice = async (backtest, symbol, context) => {
|
|
15660
|
+
this.loggerService.log("strategyCoreService getPositionMaxDrawdownPrice", {
|
|
15661
|
+
symbol,
|
|
15662
|
+
context,
|
|
15663
|
+
});
|
|
15664
|
+
await this.validate(context);
|
|
15665
|
+
return await this.strategyConnectionService.getPositionMaxDrawdownPrice(backtest, symbol, context);
|
|
15666
|
+
};
|
|
15667
|
+
/**
|
|
15668
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
15669
|
+
*
|
|
15670
|
+
* @param backtest - Whether running in backtest mode
|
|
15671
|
+
* @param symbol - Trading pair symbol
|
|
15672
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15673
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
15674
|
+
*/
|
|
15675
|
+
this.getPositionMaxDrawdownTimestamp = async (backtest, symbol, context) => {
|
|
15676
|
+
this.loggerService.log("strategyCoreService getPositionMaxDrawdownTimestamp", {
|
|
15677
|
+
symbol,
|
|
15678
|
+
context,
|
|
15679
|
+
});
|
|
15680
|
+
await this.validate(context);
|
|
15681
|
+
return await this.strategyConnectionService.getPositionMaxDrawdownTimestamp(backtest, symbol, context);
|
|
15682
|
+
};
|
|
15683
|
+
/**
|
|
15684
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
15685
|
+
*
|
|
15686
|
+
* @param backtest - Whether running in backtest mode
|
|
15687
|
+
* @param symbol - Trading pair symbol
|
|
15688
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15689
|
+
* @returns Promise resolving to PnL percentage or null
|
|
15690
|
+
*/
|
|
15691
|
+
this.getPositionMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
15692
|
+
this.loggerService.log("strategyCoreService getPositionMaxDrawdownPnlPercentage", {
|
|
15693
|
+
symbol,
|
|
15694
|
+
context,
|
|
15695
|
+
});
|
|
15696
|
+
await this.validate(context);
|
|
15697
|
+
return await this.strategyConnectionService.getPositionMaxDrawdownPnlPercentage(backtest, symbol, context);
|
|
15698
|
+
};
|
|
15699
|
+
/**
|
|
15700
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
15701
|
+
*
|
|
15702
|
+
* @param backtest - Whether running in backtest mode
|
|
15703
|
+
* @param symbol - Trading pair symbol
|
|
15704
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15705
|
+
* @returns Promise resolving to PnL cost or null
|
|
15706
|
+
*/
|
|
15707
|
+
this.getPositionMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
15708
|
+
this.loggerService.log("strategyCoreService getPositionMaxDrawdownPnlCost", {
|
|
15709
|
+
symbol,
|
|
15710
|
+
context,
|
|
15711
|
+
});
|
|
15712
|
+
await this.validate(context);
|
|
15713
|
+
return await this.strategyConnectionService.getPositionMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15714
|
+
};
|
|
15291
15715
|
}
|
|
15292
15716
|
}
|
|
15293
15717
|
|
|
@@ -15360,7 +15784,7 @@ class SizingGlobalService {
|
|
|
15360
15784
|
* @param context - Context with riskName, exchangeName, frameName
|
|
15361
15785
|
* @returns Unique string key for memoization
|
|
15362
15786
|
*/
|
|
15363
|
-
const CREATE_KEY_FN$
|
|
15787
|
+
const CREATE_KEY_FN$n = (context) => {
|
|
15364
15788
|
const parts = [context.riskName, context.exchangeName];
|
|
15365
15789
|
if (context.frameName)
|
|
15366
15790
|
parts.push(context.frameName);
|
|
@@ -15386,7 +15810,7 @@ class RiskGlobalService {
|
|
|
15386
15810
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
15387
15811
|
* @returns Promise that resolves when validation is complete
|
|
15388
15812
|
*/
|
|
15389
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15813
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
|
|
15390
15814
|
this.loggerService.log("riskGlobalService validate", {
|
|
15391
15815
|
context,
|
|
15392
15816
|
});
|
|
@@ -15464,7 +15888,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
15464
15888
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15465
15889
|
* @returns Unique string key for memoization
|
|
15466
15890
|
*/
|
|
15467
|
-
const CREATE_KEY_FN$
|
|
15891
|
+
const CREATE_KEY_FN$m = (context) => {
|
|
15468
15892
|
const parts = [context.strategyName, context.exchangeName];
|
|
15469
15893
|
if (context.frameName)
|
|
15470
15894
|
parts.push(context.frameName);
|
|
@@ -15508,7 +15932,7 @@ class ActionCoreService {
|
|
|
15508
15932
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
15509
15933
|
* @returns Promise that resolves when all validations complete
|
|
15510
15934
|
*/
|
|
15511
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
15935
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$m(context), async (context) => {
|
|
15512
15936
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
15513
15937
|
context,
|
|
15514
15938
|
});
|
|
@@ -18055,6 +18479,21 @@ const backtest_columns = [
|
|
|
18055
18479
|
format: (data) => new Date(data.closeTimestamp).toISOString(),
|
|
18056
18480
|
isVisible: () => true,
|
|
18057
18481
|
},
|
|
18482
|
+
{
|
|
18483
|
+
key: "peakPnl",
|
|
18484
|
+
label: "Peak PNL",
|
|
18485
|
+
format: (data) => {
|
|
18486
|
+
const v = data.signal._peak?.pnlPercentage;
|
|
18487
|
+
return `${v > 0 ? "+" : ""}${v.toFixed(2)}%`;
|
|
18488
|
+
},
|
|
18489
|
+
isVisible: () => true,
|
|
18490
|
+
},
|
|
18491
|
+
{
|
|
18492
|
+
key: "fallPnl",
|
|
18493
|
+
label: "Max DD PNL",
|
|
18494
|
+
format: (data) => `${data.signal._fall?.pnlPercentage.toFixed(2)}%`,
|
|
18495
|
+
isVisible: () => true,
|
|
18496
|
+
},
|
|
18058
18497
|
];
|
|
18059
18498
|
|
|
18060
18499
|
/**
|
|
@@ -18165,6 +18604,18 @@ const heat_columns = [
|
|
|
18165
18604
|
format: (data) => data.totalTrades.toString(),
|
|
18166
18605
|
isVisible: () => true,
|
|
18167
18606
|
},
|
|
18607
|
+
{
|
|
18608
|
+
key: "avgPeakPnl",
|
|
18609
|
+
label: "Avg Peak PNL",
|
|
18610
|
+
format: (data) => data.avgPeakPnl !== null ? str(data.avgPeakPnl, "%") : "N/A",
|
|
18611
|
+
isVisible: () => true,
|
|
18612
|
+
},
|
|
18613
|
+
{
|
|
18614
|
+
key: "avgFallPnl",
|
|
18615
|
+
label: "Avg DD PNL",
|
|
18616
|
+
format: (data) => data.avgFallPnl !== null ? str(data.avgFallPnl, "%") : "N/A",
|
|
18617
|
+
isVisible: () => true,
|
|
18618
|
+
},
|
|
18168
18619
|
];
|
|
18169
18620
|
|
|
18170
18621
|
/**
|
|
@@ -18362,6 +18813,22 @@ const live_columns = [
|
|
|
18362
18813
|
format: (data) => data.scheduledAt !== undefined ? new Date(data.scheduledAt).toISOString() : "N/A",
|
|
18363
18814
|
isVisible: () => true,
|
|
18364
18815
|
},
|
|
18816
|
+
{
|
|
18817
|
+
key: "peakPnl",
|
|
18818
|
+
label: "Peak PNL",
|
|
18819
|
+
format: (data) => {
|
|
18820
|
+
if (data.peakPnl === undefined)
|
|
18821
|
+
return "N/A";
|
|
18822
|
+
return `${data.peakPnl > 0 ? "+" : ""}${data.peakPnl.toFixed(2)}%`;
|
|
18823
|
+
},
|
|
18824
|
+
isVisible: () => true,
|
|
18825
|
+
},
|
|
18826
|
+
{
|
|
18827
|
+
key: "fallPnl",
|
|
18828
|
+
label: "Max DD PNL",
|
|
18829
|
+
format: (data) => data.fallPnl !== undefined ? `${data.fallPnl.toFixed(2)}%` : "N/A",
|
|
18830
|
+
isVisible: () => true,
|
|
18831
|
+
},
|
|
18365
18832
|
];
|
|
18366
18833
|
|
|
18367
18834
|
/**
|
|
@@ -19523,6 +19990,93 @@ const highest_profit_columns = [
|
|
|
19523
19990
|
},
|
|
19524
19991
|
];
|
|
19525
19992
|
|
|
19993
|
+
/**
|
|
19994
|
+
* Column configuration for max drawdown markdown reports.
|
|
19995
|
+
*
|
|
19996
|
+
* Defines the table structure for displaying max-drawdown-record events.
|
|
19997
|
+
*
|
|
19998
|
+
* @see MaxDrawdownMarkdownService
|
|
19999
|
+
* @see ColumnModel
|
|
20000
|
+
* @see MaxDrawdownEvent
|
|
20001
|
+
*/
|
|
20002
|
+
const max_drawdown_columns = [
|
|
20003
|
+
{
|
|
20004
|
+
key: "symbol",
|
|
20005
|
+
label: "Symbol",
|
|
20006
|
+
format: (data) => data.symbol,
|
|
20007
|
+
isVisible: () => true,
|
|
20008
|
+
},
|
|
20009
|
+
{
|
|
20010
|
+
key: "strategyName",
|
|
20011
|
+
label: "Strategy",
|
|
20012
|
+
format: (data) => data.strategyName,
|
|
20013
|
+
isVisible: () => true,
|
|
20014
|
+
},
|
|
20015
|
+
{
|
|
20016
|
+
key: "signalId",
|
|
20017
|
+
label: "Signal ID",
|
|
20018
|
+
format: (data) => data.signalId,
|
|
20019
|
+
isVisible: () => true,
|
|
20020
|
+
},
|
|
20021
|
+
{
|
|
20022
|
+
key: "position",
|
|
20023
|
+
label: "Position",
|
|
20024
|
+
format: (data) => data.position.toUpperCase(),
|
|
20025
|
+
isVisible: () => true,
|
|
20026
|
+
},
|
|
20027
|
+
{
|
|
20028
|
+
key: "pnl",
|
|
20029
|
+
label: "PNL (net)",
|
|
20030
|
+
format: (data) => {
|
|
20031
|
+
const pnlPercentage = data.pnl.pnlPercentage;
|
|
20032
|
+
return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
|
|
20033
|
+
},
|
|
20034
|
+
isVisible: () => true,
|
|
20035
|
+
},
|
|
20036
|
+
{
|
|
20037
|
+
key: "pnlCost",
|
|
20038
|
+
label: "PNL (USD)",
|
|
20039
|
+
format: (data) => `${data.pnl.pnlCost > 0 ? "+" : ""}${data.pnl.pnlCost.toFixed(2)} USD`,
|
|
20040
|
+
isVisible: () => true,
|
|
20041
|
+
},
|
|
20042
|
+
{
|
|
20043
|
+
key: "currentPrice",
|
|
20044
|
+
label: "DD Price",
|
|
20045
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
20046
|
+
isVisible: () => true,
|
|
20047
|
+
},
|
|
20048
|
+
{
|
|
20049
|
+
key: "priceOpen",
|
|
20050
|
+
label: "Entry Price",
|
|
20051
|
+
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
20052
|
+
isVisible: () => true,
|
|
20053
|
+
},
|
|
20054
|
+
{
|
|
20055
|
+
key: "priceTakeProfit",
|
|
20056
|
+
label: "Take Profit",
|
|
20057
|
+
format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
|
|
20058
|
+
isVisible: () => true,
|
|
20059
|
+
},
|
|
20060
|
+
{
|
|
20061
|
+
key: "priceStopLoss",
|
|
20062
|
+
label: "Stop Loss",
|
|
20063
|
+
format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
|
|
20064
|
+
isVisible: () => true,
|
|
20065
|
+
},
|
|
20066
|
+
{
|
|
20067
|
+
key: "timestamp",
|
|
20068
|
+
label: "Timestamp",
|
|
20069
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
20070
|
+
isVisible: () => true,
|
|
20071
|
+
},
|
|
20072
|
+
{
|
|
20073
|
+
key: "mode",
|
|
20074
|
+
label: "Mode",
|
|
20075
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
20076
|
+
isVisible: () => true,
|
|
20077
|
+
},
|
|
20078
|
+
];
|
|
20079
|
+
|
|
19526
20080
|
/**
|
|
19527
20081
|
* Column configuration for walker strategy comparison table in markdown reports.
|
|
19528
20082
|
*
|
|
@@ -19758,6 +20312,8 @@ const COLUMN_CONFIG = {
|
|
|
19758
20312
|
sync_columns,
|
|
19759
20313
|
/** Columns for highest profit milestone tracking events */
|
|
19760
20314
|
highest_profit_columns,
|
|
20315
|
+
/** Columns for max drawdown milestone tracking events */
|
|
20316
|
+
max_drawdown_columns,
|
|
19761
20317
|
/** Walker: PnL summary columns */
|
|
19762
20318
|
walker_pnl_columns,
|
|
19763
20319
|
/** Walker: strategy-level summary columns */
|
|
@@ -19802,6 +20358,7 @@ const WILDCARD_TARGET$1 = {
|
|
|
19802
20358
|
walker: true,
|
|
19803
20359
|
sync: true,
|
|
19804
20360
|
highest_profit: true,
|
|
20361
|
+
max_drawdown: true,
|
|
19805
20362
|
};
|
|
19806
20363
|
/**
|
|
19807
20364
|
* JSONL-based markdown adapter with append-only writes.
|
|
@@ -20029,7 +20586,7 @@ class MarkdownUtils {
|
|
|
20029
20586
|
*
|
|
20030
20587
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
20031
20588
|
*/
|
|
20032
|
-
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) => {
|
|
20589
|
+
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, max_drawdown = false, } = WILDCARD_TARGET$1) => {
|
|
20033
20590
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
|
|
20034
20591
|
backtest: bt$1,
|
|
20035
20592
|
breakeven,
|
|
@@ -20081,6 +20638,9 @@ class MarkdownUtils {
|
|
|
20081
20638
|
if (highest_profit) {
|
|
20082
20639
|
unList.push(bt.highestProfitMarkdownService.subscribe());
|
|
20083
20640
|
}
|
|
20641
|
+
if (max_drawdown) {
|
|
20642
|
+
unList.push(bt.maxDrawdownMarkdownService.subscribe());
|
|
20643
|
+
}
|
|
20084
20644
|
return compose(...unList.map((un) => () => void un()));
|
|
20085
20645
|
};
|
|
20086
20646
|
/**
|
|
@@ -20120,7 +20680,7 @@ class MarkdownUtils {
|
|
|
20120
20680
|
* Markdown.disable();
|
|
20121
20681
|
* ```
|
|
20122
20682
|
*/
|
|
20123
|
-
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) => {
|
|
20683
|
+
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, max_drawdown = false, } = WILDCARD_TARGET$1) => {
|
|
20124
20684
|
bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
|
|
20125
20685
|
backtest: bt$1,
|
|
20126
20686
|
breakeven,
|
|
@@ -20171,6 +20731,9 @@ class MarkdownUtils {
|
|
|
20171
20731
|
if (highest_profit) {
|
|
20172
20732
|
bt.highestProfitMarkdownService.unsubscribe();
|
|
20173
20733
|
}
|
|
20734
|
+
if (max_drawdown) {
|
|
20735
|
+
bt.maxDrawdownMarkdownService.unsubscribe();
|
|
20736
|
+
}
|
|
20174
20737
|
};
|
|
20175
20738
|
}
|
|
20176
20739
|
}
|
|
@@ -20286,7 +20849,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
20286
20849
|
* @param backtest - Whether running in backtest mode
|
|
20287
20850
|
* @returns Unique string key for memoization
|
|
20288
20851
|
*/
|
|
20289
|
-
const CREATE_KEY_FN$
|
|
20852
|
+
const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20290
20853
|
const parts = [symbol, strategyName, exchangeName];
|
|
20291
20854
|
if (frameName)
|
|
20292
20855
|
parts.push(frameName);
|
|
@@ -20303,7 +20866,7 @@ const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20303
20866
|
* @param timestamp - Unix timestamp in milliseconds
|
|
20304
20867
|
* @returns Filename string
|
|
20305
20868
|
*/
|
|
20306
|
-
const CREATE_FILE_NAME_FN$
|
|
20869
|
+
const CREATE_FILE_NAME_FN$c = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20307
20870
|
const parts = [symbol, strategyName, exchangeName];
|
|
20308
20871
|
if (frameName) {
|
|
20309
20872
|
parts.push(frameName);
|
|
@@ -20335,7 +20898,7 @@ function isUnsafe$3(value) {
|
|
|
20335
20898
|
* Storage class for accumulating closed signals per strategy.
|
|
20336
20899
|
* Maintains a list of all closed signals and provides methods to generate reports.
|
|
20337
20900
|
*/
|
|
20338
|
-
let ReportStorage$
|
|
20901
|
+
let ReportStorage$a = class ReportStorage {
|
|
20339
20902
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
20340
20903
|
this.symbol = symbol;
|
|
20341
20904
|
this.strategyName = strategyName;
|
|
@@ -20377,6 +20940,8 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
20377
20940
|
annualizedSharpeRatio: null,
|
|
20378
20941
|
certaintyRatio: null,
|
|
20379
20942
|
expectedYearlyReturns: null,
|
|
20943
|
+
avgPeakPnl: null,
|
|
20944
|
+
avgFallPnl: null,
|
|
20380
20945
|
};
|
|
20381
20946
|
}
|
|
20382
20947
|
const totalSignals = this._signalList.length;
|
|
@@ -20407,6 +20972,9 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
20407
20972
|
const avgDurationDays = avgDurationMs / (1000 * 60 * 60 * 24);
|
|
20408
20973
|
const tradesPerYear = avgDurationDays > 0 ? 365 / avgDurationDays : 0;
|
|
20409
20974
|
const expectedYearlyReturns = avgPnl * tradesPerYear;
|
|
20975
|
+
// Calculate average peak and fall PNL across all signals
|
|
20976
|
+
const avgPeakPnl = this._signalList.reduce((sum, s) => sum + (s.signal._peak?.pnlPercentage ?? 0), 0) / totalSignals;
|
|
20977
|
+
const avgFallPnl = this._signalList.reduce((sum, s) => sum + (s.signal._fall?.pnlPercentage ?? 0), 0) / totalSignals;
|
|
20410
20978
|
return {
|
|
20411
20979
|
signalList: this._signalList,
|
|
20412
20980
|
totalSignals,
|
|
@@ -20420,6 +20988,8 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
20420
20988
|
annualizedSharpeRatio: isUnsafe$3(annualizedSharpeRatio) ? null : annualizedSharpeRatio,
|
|
20421
20989
|
certaintyRatio: isUnsafe$3(certaintyRatio) ? null : certaintyRatio,
|
|
20422
20990
|
expectedYearlyReturns: isUnsafe$3(expectedYearlyReturns) ? null : expectedYearlyReturns,
|
|
20991
|
+
avgPeakPnl: isUnsafe$3(avgPeakPnl) ? null : avgPeakPnl,
|
|
20992
|
+
avgFallPnl: isUnsafe$3(avgFallPnl) ? null : avgFallPnl,
|
|
20423
20993
|
};
|
|
20424
20994
|
}
|
|
20425
20995
|
/**
|
|
@@ -20464,6 +21034,8 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
20464
21034
|
`**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`,
|
|
20465
21035
|
`**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`,
|
|
20466
21036
|
`**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`,
|
|
21037
|
+
`**Avg Peak PNL:** ${stats.avgPeakPnl === null ? "N/A" : `${stats.avgPeakPnl > 0 ? "+" : ""}${stats.avgPeakPnl.toFixed(2)}% (higher is better)`}`,
|
|
21038
|
+
`**Avg Max Drawdown PNL:** ${stats.avgFallPnl === null ? "N/A" : `${stats.avgFallPnl.toFixed(2)}% (closer to 0 is better)`}`,
|
|
20467
21039
|
].join("\n");
|
|
20468
21040
|
}
|
|
20469
21041
|
/**
|
|
@@ -20476,7 +21048,7 @@ let ReportStorage$9 = class ReportStorage {
|
|
|
20476
21048
|
async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
20477
21049
|
const markdown = await this.getReport(strategyName, columns);
|
|
20478
21050
|
const timestamp = getContextTimestamp();
|
|
20479
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21051
|
+
const filename = CREATE_FILE_NAME_FN$c(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
20480
21052
|
await Markdown.writeData("backtest", markdown, {
|
|
20481
21053
|
path,
|
|
20482
21054
|
file: filename,
|
|
@@ -20523,7 +21095,7 @@ class BacktestMarkdownService {
|
|
|
20523
21095
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20524
21096
|
* Each combination gets its own isolated storage instance.
|
|
20525
21097
|
*/
|
|
20526
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21098
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
|
|
20527
21099
|
/**
|
|
20528
21100
|
* Processes tick events and accumulates closed signals.
|
|
20529
21101
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -20680,7 +21252,7 @@ class BacktestMarkdownService {
|
|
|
20680
21252
|
payload,
|
|
20681
21253
|
});
|
|
20682
21254
|
if (payload) {
|
|
20683
|
-
const key = CREATE_KEY_FN$
|
|
21255
|
+
const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20684
21256
|
this.getStorage.clear(key);
|
|
20685
21257
|
}
|
|
20686
21258
|
else {
|
|
@@ -20742,7 +21314,7 @@ class BacktestMarkdownService {
|
|
|
20742
21314
|
* @param backtest - Whether running in backtest mode
|
|
20743
21315
|
* @returns Unique string key for memoization
|
|
20744
21316
|
*/
|
|
20745
|
-
const CREATE_KEY_FN$
|
|
21317
|
+
const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20746
21318
|
const parts = [symbol, strategyName, exchangeName];
|
|
20747
21319
|
if (frameName)
|
|
20748
21320
|
parts.push(frameName);
|
|
@@ -20759,7 +21331,7 @@ const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
20759
21331
|
* @param timestamp - Unix timestamp in milliseconds
|
|
20760
21332
|
* @returns Filename string
|
|
20761
21333
|
*/
|
|
20762
|
-
const CREATE_FILE_NAME_FN$
|
|
21334
|
+
const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
20763
21335
|
const parts = [symbol, strategyName, exchangeName];
|
|
20764
21336
|
if (frameName) {
|
|
20765
21337
|
parts.push(frameName);
|
|
@@ -20791,7 +21363,7 @@ function isUnsafe$2(value) {
|
|
|
20791
21363
|
* Storage class for accumulating all tick events per strategy.
|
|
20792
21364
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
20793
21365
|
*/
|
|
20794
|
-
let ReportStorage$
|
|
21366
|
+
let ReportStorage$9 = class ReportStorage {
|
|
20795
21367
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
20796
21368
|
this.symbol = symbol;
|
|
20797
21369
|
this.strategyName = strategyName;
|
|
@@ -20931,6 +21503,8 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
20931
21503
|
duration: durationMin,
|
|
20932
21504
|
pendingAt: data.signal.pendingAt,
|
|
20933
21505
|
scheduledAt: data.signal.scheduledAt,
|
|
21506
|
+
peakPnl: data.signal._peak?.pnlPercentage,
|
|
21507
|
+
fallPnl: data.signal._fall?.pnlPercentage,
|
|
20934
21508
|
};
|
|
20935
21509
|
this._eventList.unshift(newEvent);
|
|
20936
21510
|
// Trim queue if exceeded GLOBAL_CONFIG.CC_MAX_LIVE_MARKDOWN_ROWS
|
|
@@ -21060,6 +21634,8 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
21060
21634
|
annualizedSharpeRatio: null,
|
|
21061
21635
|
certaintyRatio: null,
|
|
21062
21636
|
expectedYearlyReturns: null,
|
|
21637
|
+
avgPeakPnl: null,
|
|
21638
|
+
avgFallPnl: null,
|
|
21063
21639
|
};
|
|
21064
21640
|
}
|
|
21065
21641
|
const closedEvents = this._eventList.filter((e) => e.action === "closed");
|
|
@@ -21103,6 +21679,12 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
21103
21679
|
const tradesPerYear = avgDurationDays > 0 ? 365 / avgDurationDays : 0;
|
|
21104
21680
|
expectedYearlyReturns = avgPnl * tradesPerYear;
|
|
21105
21681
|
}
|
|
21682
|
+
const avgPeakPnl = totalClosed > 0
|
|
21683
|
+
? closedEvents.reduce((sum, e) => sum + (e.peakPnl || 0), 0) / totalClosed
|
|
21684
|
+
: 0;
|
|
21685
|
+
const avgFallPnl = totalClosed > 0
|
|
21686
|
+
? closedEvents.reduce((sum, e) => sum + (e.fallPnl || 0), 0) / totalClosed
|
|
21687
|
+
: 0;
|
|
21106
21688
|
return {
|
|
21107
21689
|
eventList: this._eventList,
|
|
21108
21690
|
totalEvents: this._eventList.length,
|
|
@@ -21117,6 +21699,8 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
21117
21699
|
annualizedSharpeRatio: isUnsafe$2(annualizedSharpeRatio) ? null : annualizedSharpeRatio,
|
|
21118
21700
|
certaintyRatio: isUnsafe$2(certaintyRatio) ? null : certaintyRatio,
|
|
21119
21701
|
expectedYearlyReturns: isUnsafe$2(expectedYearlyReturns) ? null : expectedYearlyReturns,
|
|
21702
|
+
avgPeakPnl: isUnsafe$2(avgPeakPnl) ? null : avgPeakPnl,
|
|
21703
|
+
avgFallPnl: isUnsafe$2(avgFallPnl) ? null : avgFallPnl,
|
|
21120
21704
|
};
|
|
21121
21705
|
}
|
|
21122
21706
|
/**
|
|
@@ -21161,6 +21745,8 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
21161
21745
|
`**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`,
|
|
21162
21746
|
`**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`,
|
|
21163
21747
|
`**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`,
|
|
21748
|
+
`**Avg Peak PNL:** ${stats.avgPeakPnl === null ? "N/A" : `${stats.avgPeakPnl > 0 ? "+" : ""}${stats.avgPeakPnl.toFixed(2)}% (higher is better)`}`,
|
|
21749
|
+
`**Avg Max Drawdown PNL:** ${stats.avgFallPnl === null ? "N/A" : `${stats.avgFallPnl.toFixed(2)}% (closer to 0 is better)`}`,
|
|
21164
21750
|
].join("\n");
|
|
21165
21751
|
}
|
|
21166
21752
|
/**
|
|
@@ -21173,7 +21759,7 @@ let ReportStorage$8 = class ReportStorage {
|
|
|
21173
21759
|
async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
21174
21760
|
const markdown = await this.getReport(strategyName, columns);
|
|
21175
21761
|
const timestamp = getContextTimestamp();
|
|
21176
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
21762
|
+
const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
21177
21763
|
await Markdown.writeData("live", markdown, {
|
|
21178
21764
|
path,
|
|
21179
21765
|
signalId: "",
|
|
@@ -21223,7 +21809,7 @@ class LiveMarkdownService {
|
|
|
21223
21809
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21224
21810
|
* Each combination gets its own isolated storage instance.
|
|
21225
21811
|
*/
|
|
21226
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21812
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
|
|
21227
21813
|
/**
|
|
21228
21814
|
* Subscribes to live signal emitter to receive tick events.
|
|
21229
21815
|
* Protected against multiple subscriptions.
|
|
@@ -21441,7 +22027,7 @@ class LiveMarkdownService {
|
|
|
21441
22027
|
payload,
|
|
21442
22028
|
});
|
|
21443
22029
|
if (payload) {
|
|
21444
|
-
const key = CREATE_KEY_FN$
|
|
22030
|
+
const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21445
22031
|
this.getStorage.clear(key);
|
|
21446
22032
|
}
|
|
21447
22033
|
else {
|
|
@@ -21461,7 +22047,7 @@ class LiveMarkdownService {
|
|
|
21461
22047
|
* @param backtest - Whether running in backtest mode
|
|
21462
22048
|
* @returns Unique string key for memoization
|
|
21463
22049
|
*/
|
|
21464
|
-
const CREATE_KEY_FN$
|
|
22050
|
+
const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21465
22051
|
const parts = [symbol, strategyName, exchangeName];
|
|
21466
22052
|
if (frameName)
|
|
21467
22053
|
parts.push(frameName);
|
|
@@ -21478,7 +22064,7 @@ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
21478
22064
|
* @param timestamp - Unix timestamp in milliseconds
|
|
21479
22065
|
* @returns Filename string
|
|
21480
22066
|
*/
|
|
21481
|
-
const CREATE_FILE_NAME_FN$
|
|
22067
|
+
const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
21482
22068
|
const parts = [symbol, strategyName, exchangeName];
|
|
21483
22069
|
if (frameName) {
|
|
21484
22070
|
parts.push(frameName);
|
|
@@ -21492,7 +22078,7 @@ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
21492
22078
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
21493
22079
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
21494
22080
|
*/
|
|
21495
|
-
let ReportStorage$
|
|
22081
|
+
let ReportStorage$8 = class ReportStorage {
|
|
21496
22082
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
21497
22083
|
this.symbol = symbol;
|
|
21498
22084
|
this.strategyName = strategyName;
|
|
@@ -21709,7 +22295,7 @@ let ReportStorage$7 = class ReportStorage {
|
|
|
21709
22295
|
async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
21710
22296
|
const markdown = await this.getReport(strategyName, columns);
|
|
21711
22297
|
const timestamp = getContextTimestamp();
|
|
21712
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
22298
|
+
const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
21713
22299
|
await Markdown.writeData("schedule", markdown, {
|
|
21714
22300
|
path,
|
|
21715
22301
|
file: filename,
|
|
@@ -21750,7 +22336,7 @@ class ScheduleMarkdownService {
|
|
|
21750
22336
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
21751
22337
|
* Each combination gets its own isolated storage instance.
|
|
21752
22338
|
*/
|
|
21753
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22339
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
21754
22340
|
/**
|
|
21755
22341
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
21756
22342
|
* Protected against multiple subscriptions.
|
|
@@ -21953,7 +22539,7 @@ class ScheduleMarkdownService {
|
|
|
21953
22539
|
payload,
|
|
21954
22540
|
});
|
|
21955
22541
|
if (payload) {
|
|
21956
|
-
const key = CREATE_KEY_FN$
|
|
22542
|
+
const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
21957
22543
|
this.getStorage.clear(key);
|
|
21958
22544
|
}
|
|
21959
22545
|
else {
|
|
@@ -21973,7 +22559,7 @@ class ScheduleMarkdownService {
|
|
|
21973
22559
|
* @param backtest - Whether running in backtest mode
|
|
21974
22560
|
* @returns Unique string key for memoization
|
|
21975
22561
|
*/
|
|
21976
|
-
const CREATE_KEY_FN$
|
|
22562
|
+
const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
21977
22563
|
const parts = [symbol, strategyName, exchangeName];
|
|
21978
22564
|
if (frameName)
|
|
21979
22565
|
parts.push(frameName);
|
|
@@ -21990,7 +22576,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
21990
22576
|
* @param timestamp - Unix timestamp in milliseconds
|
|
21991
22577
|
* @returns Filename string
|
|
21992
22578
|
*/
|
|
21993
|
-
const CREATE_FILE_NAME_FN$
|
|
22579
|
+
const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
21994
22580
|
const parts = [symbol, strategyName, exchangeName];
|
|
21995
22581
|
if (frameName) {
|
|
21996
22582
|
parts.push(frameName);
|
|
@@ -22171,7 +22757,7 @@ class PerformanceStorage {
|
|
|
22171
22757
|
async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
22172
22758
|
const markdown = await this.getReport(strategyName, columns);
|
|
22173
22759
|
const timestamp = getContextTimestamp();
|
|
22174
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
22760
|
+
const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
22175
22761
|
await Markdown.writeData("performance", markdown, {
|
|
22176
22762
|
path,
|
|
22177
22763
|
file: filename,
|
|
@@ -22218,7 +22804,7 @@ class PerformanceMarkdownService {
|
|
|
22218
22804
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
22219
22805
|
* Each combination gets its own isolated storage instance.
|
|
22220
22806
|
*/
|
|
22221
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
22807
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
22222
22808
|
/**
|
|
22223
22809
|
* Subscribes to performance emitter to receive performance events.
|
|
22224
22810
|
* Protected against multiple subscriptions.
|
|
@@ -22385,7 +22971,7 @@ class PerformanceMarkdownService {
|
|
|
22385
22971
|
payload,
|
|
22386
22972
|
});
|
|
22387
22973
|
if (payload) {
|
|
22388
|
-
const key = CREATE_KEY_FN$
|
|
22974
|
+
const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
22389
22975
|
this.getStorage.clear(key);
|
|
22390
22976
|
}
|
|
22391
22977
|
else {
|
|
@@ -22399,7 +22985,7 @@ class PerformanceMarkdownService {
|
|
|
22399
22985
|
* Creates a filename for markdown report based on walker name.
|
|
22400
22986
|
* Filename format: "walkerName-timestamp.md"
|
|
22401
22987
|
*/
|
|
22402
|
-
const CREATE_FILE_NAME_FN$
|
|
22988
|
+
const CREATE_FILE_NAME_FN$8 = (walkerName, timestamp) => {
|
|
22403
22989
|
return `${walkerName}-${timestamp}.md`;
|
|
22404
22990
|
};
|
|
22405
22991
|
/**
|
|
@@ -22434,7 +23020,7 @@ function formatMetric(value) {
|
|
|
22434
23020
|
* Storage class for accumulating walker results.
|
|
22435
23021
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
22436
23022
|
*/
|
|
22437
|
-
let ReportStorage$
|
|
23023
|
+
let ReportStorage$7 = class ReportStorage {
|
|
22438
23024
|
constructor(walkerName) {
|
|
22439
23025
|
this.walkerName = walkerName;
|
|
22440
23026
|
/** Walker metadata (set from first addResult call) */
|
|
@@ -22631,7 +23217,7 @@ let ReportStorage$6 = class ReportStorage {
|
|
|
22631
23217
|
async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
22632
23218
|
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
22633
23219
|
const timestamp = getContextTimestamp();
|
|
22634
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
23220
|
+
const filename = CREATE_FILE_NAME_FN$8(this.walkerName, timestamp);
|
|
22635
23221
|
await Markdown.writeData("walker", markdown, {
|
|
22636
23222
|
path,
|
|
22637
23223
|
file: filename,
|
|
@@ -22667,7 +23253,7 @@ class WalkerMarkdownService {
|
|
|
22667
23253
|
* Memoized function to get or create ReportStorage for a walker.
|
|
22668
23254
|
* Each walker gets its own isolated storage instance.
|
|
22669
23255
|
*/
|
|
22670
|
-
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$
|
|
23256
|
+
this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$7(walkerName));
|
|
22671
23257
|
/**
|
|
22672
23258
|
* Subscribes to walker emitter to receive walker progress events.
|
|
22673
23259
|
* Protected against multiple subscriptions.
|
|
@@ -22864,7 +23450,7 @@ class WalkerMarkdownService {
|
|
|
22864
23450
|
* @param backtest - Whether running in backtest mode
|
|
22865
23451
|
* @returns Unique string key for memoization
|
|
22866
23452
|
*/
|
|
22867
|
-
const CREATE_KEY_FN$
|
|
23453
|
+
const CREATE_KEY_FN$h = (exchangeName, frameName, backtest) => {
|
|
22868
23454
|
const parts = [exchangeName];
|
|
22869
23455
|
if (frameName)
|
|
22870
23456
|
parts.push(frameName);
|
|
@@ -22875,7 +23461,7 @@ const CREATE_KEY_FN$g = (exchangeName, frameName, backtest) => {
|
|
|
22875
23461
|
* Creates a filename for markdown report based on memoization key components.
|
|
22876
23462
|
* Filename format: "strategyName_exchangeName_frameName-timestamp.md"
|
|
22877
23463
|
*/
|
|
22878
|
-
const CREATE_FILE_NAME_FN$
|
|
23464
|
+
const CREATE_FILE_NAME_FN$7 = (strategyName, exchangeName, frameName, timestamp) => {
|
|
22879
23465
|
const parts = [strategyName, exchangeName];
|
|
22880
23466
|
if (frameName) {
|
|
22881
23467
|
parts.push(frameName);
|
|
@@ -23065,6 +23651,13 @@ class HeatmapStorage {
|
|
|
23065
23651
|
const lossRate = 100 - winRate;
|
|
23066
23652
|
expectancy = (winRate / 100) * avgWin + (lossRate / 100) * avgLoss;
|
|
23067
23653
|
}
|
|
23654
|
+
// Calculate average peak and fall PNL
|
|
23655
|
+
let avgPeakPnl = null;
|
|
23656
|
+
let avgFallPnl = null;
|
|
23657
|
+
if (signals.length > 0) {
|
|
23658
|
+
avgPeakPnl = signals.reduce((acc, s) => acc + (s.signal._peak?.pnlPercentage ?? 0), 0) / signals.length;
|
|
23659
|
+
avgFallPnl = signals.reduce((acc, s) => acc + (s.signal._fall?.pnlPercentage ?? 0), 0) / signals.length;
|
|
23660
|
+
}
|
|
23068
23661
|
// Apply safe math checks
|
|
23069
23662
|
if (isUnsafe(winRate))
|
|
23070
23663
|
winRate = null;
|
|
@@ -23086,6 +23679,10 @@ class HeatmapStorage {
|
|
|
23086
23679
|
avgLoss = null;
|
|
23087
23680
|
if (isUnsafe(expectancy))
|
|
23088
23681
|
expectancy = null;
|
|
23682
|
+
if (isUnsafe(avgPeakPnl))
|
|
23683
|
+
avgPeakPnl = null;
|
|
23684
|
+
if (isUnsafe(avgFallPnl))
|
|
23685
|
+
avgFallPnl = null;
|
|
23089
23686
|
return {
|
|
23090
23687
|
symbol,
|
|
23091
23688
|
totalPnl,
|
|
@@ -23103,6 +23700,8 @@ class HeatmapStorage {
|
|
|
23103
23700
|
maxWinStreak,
|
|
23104
23701
|
maxLossStreak,
|
|
23105
23702
|
expectancy,
|
|
23703
|
+
avgPeakPnl,
|
|
23704
|
+
avgFallPnl,
|
|
23106
23705
|
};
|
|
23107
23706
|
}
|
|
23108
23707
|
/**
|
|
@@ -23152,17 +23751,34 @@ class HeatmapStorage {
|
|
|
23152
23751
|
const weightedSum = validSharpes.reduce((acc, s) => acc + s.sharpeRatio * s.totalTrades, 0);
|
|
23153
23752
|
portfolioSharpeRatio = weightedSum / portfolioTotalTrades;
|
|
23154
23753
|
}
|
|
23754
|
+
// Calculate portfolio-wide weighted average peak/fall PNL
|
|
23755
|
+
let portfolioAvgPeakPnl = null;
|
|
23756
|
+
let portfolioAvgFallPnl = null;
|
|
23757
|
+
const validPeak = symbols.filter((s) => s.avgPeakPnl !== null);
|
|
23758
|
+
const validFall = symbols.filter((s) => s.avgFallPnl !== null);
|
|
23759
|
+
if (validPeak.length > 0 && portfolioTotalTrades > 0) {
|
|
23760
|
+
portfolioAvgPeakPnl = validPeak.reduce((acc, s) => acc + s.avgPeakPnl * s.totalTrades, 0) / portfolioTotalTrades;
|
|
23761
|
+
}
|
|
23762
|
+
if (validFall.length > 0 && portfolioTotalTrades > 0) {
|
|
23763
|
+
portfolioAvgFallPnl = validFall.reduce((acc, s) => acc + s.avgFallPnl * s.totalTrades, 0) / portfolioTotalTrades;
|
|
23764
|
+
}
|
|
23155
23765
|
// Apply safe math
|
|
23156
23766
|
if (isUnsafe(portfolioTotalPnl))
|
|
23157
23767
|
portfolioTotalPnl = null;
|
|
23158
23768
|
if (isUnsafe(portfolioSharpeRatio))
|
|
23159
23769
|
portfolioSharpeRatio = null;
|
|
23770
|
+
if (isUnsafe(portfolioAvgPeakPnl))
|
|
23771
|
+
portfolioAvgPeakPnl = null;
|
|
23772
|
+
if (isUnsafe(portfolioAvgFallPnl))
|
|
23773
|
+
portfolioAvgFallPnl = null;
|
|
23160
23774
|
return {
|
|
23161
23775
|
symbols,
|
|
23162
23776
|
totalSymbols,
|
|
23163
23777
|
portfolioTotalPnl,
|
|
23164
23778
|
portfolioSharpeRatio,
|
|
23165
23779
|
portfolioTotalTrades,
|
|
23780
|
+
portfolioAvgPeakPnl,
|
|
23781
|
+
portfolioAvgFallPnl,
|
|
23166
23782
|
};
|
|
23167
23783
|
}
|
|
23168
23784
|
/**
|
|
@@ -23211,7 +23827,7 @@ class HeatmapStorage {
|
|
|
23211
23827
|
return [
|
|
23212
23828
|
`# Portfolio Heatmap: ${strategyName}`,
|
|
23213
23829
|
"",
|
|
23214
|
-
`**Total Symbols:** ${data.totalSymbols} | **Portfolio PNL:** ${data.portfolioTotalPnl !== null ? str(data.portfolioTotalPnl, "%") : "N/A"} | **Portfolio Sharpe:** ${data.portfolioSharpeRatio !== null ? str(data.portfolioSharpeRatio) : "N/A"} | **Total Trades:** ${data.portfolioTotalTrades}`,
|
|
23830
|
+
`**Total Symbols:** ${data.totalSymbols} | **Portfolio PNL:** ${data.portfolioTotalPnl !== null ? str(data.portfolioTotalPnl, "%") : "N/A"} | **Portfolio Sharpe:** ${data.portfolioSharpeRatio !== null ? str(data.portfolioSharpeRatio) : "N/A"} | **Total Trades:** ${data.portfolioTotalTrades} | **Avg Peak PNL:** ${data.portfolioAvgPeakPnl !== null ? str(data.portfolioAvgPeakPnl, "%") : "N/A"} | **Avg Max Drawdown PNL:** ${data.portfolioAvgFallPnl !== null ? str(data.portfolioAvgFallPnl, "%") : "N/A"}`,
|
|
23215
23831
|
"",
|
|
23216
23832
|
table
|
|
23217
23833
|
].join("\n");
|
|
@@ -23235,7 +23851,7 @@ class HeatmapStorage {
|
|
|
23235
23851
|
async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
23236
23852
|
const markdown = await this.getReport(strategyName, columns);
|
|
23237
23853
|
const timestamp = getContextTimestamp();
|
|
23238
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
23854
|
+
const filename = CREATE_FILE_NAME_FN$7(strategyName, this.exchangeName, this.frameName, timestamp);
|
|
23239
23855
|
await Markdown.writeData("heat", markdown, {
|
|
23240
23856
|
path,
|
|
23241
23857
|
file: filename,
|
|
@@ -23281,7 +23897,7 @@ class HeatMarkdownService {
|
|
|
23281
23897
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
23282
23898
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
23283
23899
|
*/
|
|
23284
|
-
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23900
|
+
this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
23285
23901
|
/**
|
|
23286
23902
|
* Subscribes to signal emitter to receive tick events.
|
|
23287
23903
|
* Protected against multiple subscriptions.
|
|
@@ -23499,7 +24115,7 @@ class HeatMarkdownService {
|
|
|
23499
24115
|
payload,
|
|
23500
24116
|
});
|
|
23501
24117
|
if (payload) {
|
|
23502
|
-
const key = CREATE_KEY_FN$
|
|
24118
|
+
const key = CREATE_KEY_FN$h(payload.exchangeName, payload.frameName, payload.backtest);
|
|
23503
24119
|
this.getStorage.clear(key);
|
|
23504
24120
|
}
|
|
23505
24121
|
else {
|
|
@@ -24530,7 +25146,7 @@ class ClientPartial {
|
|
|
24530
25146
|
* @param backtest - Whether running in backtest mode
|
|
24531
25147
|
* @returns Unique string key for memoization
|
|
24532
25148
|
*/
|
|
24533
|
-
const CREATE_KEY_FN$
|
|
25149
|
+
const CREATE_KEY_FN$g = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
24534
25150
|
/**
|
|
24535
25151
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
24536
25152
|
*
|
|
@@ -24652,7 +25268,7 @@ class PartialConnectionService {
|
|
|
24652
25268
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
24653
25269
|
* Value: ClientPartial instance with logger and event emitters
|
|
24654
25270
|
*/
|
|
24655
|
-
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
25271
|
+
this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$g(signalId, backtest), (signalId, backtest) => {
|
|
24656
25272
|
return new ClientPartial({
|
|
24657
25273
|
signalId,
|
|
24658
25274
|
logger: this.loggerService,
|
|
@@ -24742,7 +25358,7 @@ class PartialConnectionService {
|
|
|
24742
25358
|
const partial = this.getPartial(data.id, backtest);
|
|
24743
25359
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
24744
25360
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
24745
|
-
const key = CREATE_KEY_FN$
|
|
25361
|
+
const key = CREATE_KEY_FN$g(data.id, backtest);
|
|
24746
25362
|
this.getPartial.clear(key);
|
|
24747
25363
|
};
|
|
24748
25364
|
}
|
|
@@ -24758,7 +25374,7 @@ class PartialConnectionService {
|
|
|
24758
25374
|
* @param backtest - Whether running in backtest mode
|
|
24759
25375
|
* @returns Unique string key for memoization
|
|
24760
25376
|
*/
|
|
24761
|
-
const CREATE_KEY_FN$
|
|
25377
|
+
const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24762
25378
|
const parts = [symbol, strategyName, exchangeName];
|
|
24763
25379
|
if (frameName)
|
|
24764
25380
|
parts.push(frameName);
|
|
@@ -24769,7 +25385,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
24769
25385
|
* Creates a filename for markdown report based on memoization key components.
|
|
24770
25386
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
24771
25387
|
*/
|
|
24772
|
-
const CREATE_FILE_NAME_FN$
|
|
25388
|
+
const CREATE_FILE_NAME_FN$6 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
24773
25389
|
const parts = [symbol, strategyName, exchangeName];
|
|
24774
25390
|
if (frameName) {
|
|
24775
25391
|
parts.push(frameName);
|
|
@@ -24783,7 +25399,7 @@ const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
24783
25399
|
* Storage class for accumulating partial profit/loss events per symbol-strategy pair.
|
|
24784
25400
|
* Maintains a chronological list of profit and loss level events.
|
|
24785
25401
|
*/
|
|
24786
|
-
let ReportStorage$
|
|
25402
|
+
let ReportStorage$6 = class ReportStorage {
|
|
24787
25403
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
24788
25404
|
this.symbol = symbol;
|
|
24789
25405
|
this.strategyName = strategyName;
|
|
@@ -24940,7 +25556,7 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
24940
25556
|
async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
24941
25557
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
24942
25558
|
const timestamp = getContextTimestamp();
|
|
24943
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
25559
|
+
const filename = CREATE_FILE_NAME_FN$6(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
24944
25560
|
await Markdown.writeData("partial", markdown, {
|
|
24945
25561
|
path,
|
|
24946
25562
|
file: filename,
|
|
@@ -24981,7 +25597,7 @@ class PartialMarkdownService {
|
|
|
24981
25597
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24982
25598
|
* Each combination gets its own isolated storage instance.
|
|
24983
25599
|
*/
|
|
24984
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
25600
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
24985
25601
|
/**
|
|
24986
25602
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
24987
25603
|
* Protected against multiple subscriptions.
|
|
@@ -25191,7 +25807,7 @@ class PartialMarkdownService {
|
|
|
25191
25807
|
payload,
|
|
25192
25808
|
});
|
|
25193
25809
|
if (payload) {
|
|
25194
|
-
const key = CREATE_KEY_FN$
|
|
25810
|
+
const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25195
25811
|
this.getStorage.clear(key);
|
|
25196
25812
|
}
|
|
25197
25813
|
else {
|
|
@@ -25207,7 +25823,7 @@ class PartialMarkdownService {
|
|
|
25207
25823
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
25208
25824
|
* @returns Unique string key for memoization
|
|
25209
25825
|
*/
|
|
25210
|
-
const CREATE_KEY_FN$
|
|
25826
|
+
const CREATE_KEY_FN$e = (context) => {
|
|
25211
25827
|
const parts = [context.strategyName, context.exchangeName];
|
|
25212
25828
|
if (context.frameName)
|
|
25213
25829
|
parts.push(context.frameName);
|
|
@@ -25281,7 +25897,7 @@ class PartialGlobalService {
|
|
|
25281
25897
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
25282
25898
|
* @param methodName - Name of the calling method for error tracking
|
|
25283
25899
|
*/
|
|
25284
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
25900
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$e(context), (context, methodName) => {
|
|
25285
25901
|
this.loggerService.log("partialGlobalService validate", {
|
|
25286
25902
|
context,
|
|
25287
25903
|
methodName,
|
|
@@ -25736,7 +26352,7 @@ class ClientBreakeven {
|
|
|
25736
26352
|
* @param backtest - Whether running in backtest mode
|
|
25737
26353
|
* @returns Unique string key for memoization
|
|
25738
26354
|
*/
|
|
25739
|
-
const CREATE_KEY_FN$
|
|
26355
|
+
const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
25740
26356
|
/**
|
|
25741
26357
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
25742
26358
|
*
|
|
@@ -25822,7 +26438,7 @@ class BreakevenConnectionService {
|
|
|
25822
26438
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
25823
26439
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
25824
26440
|
*/
|
|
25825
|
-
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
26441
|
+
this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
|
|
25826
26442
|
return new ClientBreakeven({
|
|
25827
26443
|
signalId,
|
|
25828
26444
|
logger: this.loggerService,
|
|
@@ -25883,7 +26499,7 @@ class BreakevenConnectionService {
|
|
|
25883
26499
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
25884
26500
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
25885
26501
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
25886
|
-
const key = CREATE_KEY_FN$
|
|
26502
|
+
const key = CREATE_KEY_FN$d(data.id, backtest);
|
|
25887
26503
|
this.getBreakeven.clear(key);
|
|
25888
26504
|
};
|
|
25889
26505
|
}
|
|
@@ -25899,7 +26515,7 @@ class BreakevenConnectionService {
|
|
|
25899
26515
|
* @param backtest - Whether running in backtest mode
|
|
25900
26516
|
* @returns Unique string key for memoization
|
|
25901
26517
|
*/
|
|
25902
|
-
const CREATE_KEY_FN$
|
|
26518
|
+
const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
25903
26519
|
const parts = [symbol, strategyName, exchangeName];
|
|
25904
26520
|
if (frameName)
|
|
25905
26521
|
parts.push(frameName);
|
|
@@ -25910,7 +26526,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
25910
26526
|
* Creates a filename for markdown report based on memoization key components.
|
|
25911
26527
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
25912
26528
|
*/
|
|
25913
|
-
const CREATE_FILE_NAME_FN$
|
|
26529
|
+
const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
25914
26530
|
const parts = [symbol, strategyName, exchangeName];
|
|
25915
26531
|
if (frameName) {
|
|
25916
26532
|
parts.push(frameName);
|
|
@@ -25924,7 +26540,7 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
25924
26540
|
* Storage class for accumulating breakeven events per symbol-strategy pair.
|
|
25925
26541
|
* Maintains a chronological list of breakeven events.
|
|
25926
26542
|
*/
|
|
25927
|
-
let ReportStorage$
|
|
26543
|
+
let ReportStorage$5 = class ReportStorage {
|
|
25928
26544
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
25929
26545
|
this.symbol = symbol;
|
|
25930
26546
|
this.strategyName = strategyName;
|
|
@@ -26033,7 +26649,7 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
26033
26649
|
async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
|
|
26034
26650
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
26035
26651
|
const timestamp = getContextTimestamp();
|
|
26036
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
26652
|
+
const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
26037
26653
|
await Markdown.writeData("breakeven", markdown, {
|
|
26038
26654
|
path,
|
|
26039
26655
|
file: filename,
|
|
@@ -26074,7 +26690,7 @@ class BreakevenMarkdownService {
|
|
|
26074
26690
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26075
26691
|
* Each combination gets its own isolated storage instance.
|
|
26076
26692
|
*/
|
|
26077
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
26693
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
|
|
26078
26694
|
/**
|
|
26079
26695
|
* Subscribes to breakeven signal emitter to receive events.
|
|
26080
26696
|
* Protected against multiple subscriptions.
|
|
@@ -26263,7 +26879,7 @@ class BreakevenMarkdownService {
|
|
|
26263
26879
|
payload,
|
|
26264
26880
|
});
|
|
26265
26881
|
if (payload) {
|
|
26266
|
-
const key = CREATE_KEY_FN$
|
|
26882
|
+
const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26267
26883
|
this.getStorage.clear(key);
|
|
26268
26884
|
}
|
|
26269
26885
|
else {
|
|
@@ -26279,7 +26895,7 @@ class BreakevenMarkdownService {
|
|
|
26279
26895
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
26280
26896
|
* @returns Unique string key for memoization
|
|
26281
26897
|
*/
|
|
26282
|
-
const CREATE_KEY_FN$
|
|
26898
|
+
const CREATE_KEY_FN$b = (context) => {
|
|
26283
26899
|
const parts = [context.strategyName, context.exchangeName];
|
|
26284
26900
|
if (context.frameName)
|
|
26285
26901
|
parts.push(context.frameName);
|
|
@@ -26353,7 +26969,7 @@ class BreakevenGlobalService {
|
|
|
26353
26969
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
26354
26970
|
* @param methodName - Name of the calling method for error tracking
|
|
26355
26971
|
*/
|
|
26356
|
-
this.validate = memoize(([context]) => CREATE_KEY_FN$
|
|
26972
|
+
this.validate = memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
|
|
26357
26973
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
26358
26974
|
context,
|
|
26359
26975
|
methodName,
|
|
@@ -26574,7 +27190,7 @@ class ConfigValidationService {
|
|
|
26574
27190
|
* @param backtest - Whether running in backtest mode
|
|
26575
27191
|
* @returns Unique string key for memoization
|
|
26576
27192
|
*/
|
|
26577
|
-
const CREATE_KEY_FN$
|
|
27193
|
+
const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
26578
27194
|
const parts = [symbol, strategyName, exchangeName];
|
|
26579
27195
|
if (frameName)
|
|
26580
27196
|
parts.push(frameName);
|
|
@@ -26585,7 +27201,7 @@ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
26585
27201
|
* Creates a filename for markdown report based on memoization key components.
|
|
26586
27202
|
* Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
|
|
26587
27203
|
*/
|
|
26588
|
-
const CREATE_FILE_NAME_FN$
|
|
27204
|
+
const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
26589
27205
|
const parts = [symbol, strategyName, exchangeName];
|
|
26590
27206
|
if (frameName) {
|
|
26591
27207
|
parts.push(frameName);
|
|
@@ -26599,7 +27215,7 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
26599
27215
|
* Storage class for accumulating risk rejection events per symbol-strategy pair.
|
|
26600
27216
|
* Maintains a chronological list of rejected signals due to risk limits.
|
|
26601
27217
|
*/
|
|
26602
|
-
let ReportStorage$
|
|
27218
|
+
let ReportStorage$4 = class ReportStorage {
|
|
26603
27219
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
26604
27220
|
this.symbol = symbol;
|
|
26605
27221
|
this.strategyName = strategyName;
|
|
@@ -26700,7 +27316,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
26700
27316
|
async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
26701
27317
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
26702
27318
|
const timestamp = getContextTimestamp();
|
|
26703
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
27319
|
+
const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
26704
27320
|
await Markdown.writeData("risk", markdown, {
|
|
26705
27321
|
path,
|
|
26706
27322
|
file: filename,
|
|
@@ -26741,7 +27357,7 @@ class RiskMarkdownService {
|
|
|
26741
27357
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
26742
27358
|
* Each combination gets its own isolated storage instance.
|
|
26743
27359
|
*/
|
|
26744
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
27360
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
26745
27361
|
/**
|
|
26746
27362
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
26747
27363
|
* Protected against multiple subscriptions.
|
|
@@ -26930,7 +27546,7 @@ class RiskMarkdownService {
|
|
|
26930
27546
|
payload,
|
|
26931
27547
|
});
|
|
26932
27548
|
if (payload) {
|
|
26933
|
-
const key = CREATE_KEY_FN$
|
|
27549
|
+
const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
26934
27550
|
this.getStorage.clear(key);
|
|
26935
27551
|
}
|
|
26936
27552
|
else {
|
|
@@ -27232,6 +27848,7 @@ const WILDCARD_TARGET = {
|
|
|
27232
27848
|
walker: true,
|
|
27233
27849
|
sync: true,
|
|
27234
27850
|
highest_profit: true,
|
|
27851
|
+
max_drawdown: true,
|
|
27235
27852
|
};
|
|
27236
27853
|
/**
|
|
27237
27854
|
* Utility class for managing report services.
|
|
@@ -27269,7 +27886,7 @@ class ReportUtils {
|
|
|
27269
27886
|
*
|
|
27270
27887
|
* @returns Cleanup function that unsubscribes from all enabled services
|
|
27271
27888
|
*/
|
|
27272
|
-
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) => {
|
|
27889
|
+
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, max_drawdown = false, } = WILDCARD_TARGET) => {
|
|
27273
27890
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
|
|
27274
27891
|
backtest: bt$1,
|
|
27275
27892
|
breakeven,
|
|
@@ -27320,6 +27937,9 @@ class ReportUtils {
|
|
|
27320
27937
|
if (highest_profit) {
|
|
27321
27938
|
unList.push(bt.highestProfitReportService.subscribe());
|
|
27322
27939
|
}
|
|
27940
|
+
if (max_drawdown) {
|
|
27941
|
+
unList.push(bt.maxDrawdownReportService.subscribe());
|
|
27942
|
+
}
|
|
27323
27943
|
return compose(...unList.map((un) => () => void un()));
|
|
27324
27944
|
};
|
|
27325
27945
|
/**
|
|
@@ -27358,7 +27978,7 @@ class ReportUtils {
|
|
|
27358
27978
|
* Report.disable();
|
|
27359
27979
|
* ```
|
|
27360
27980
|
*/
|
|
27361
|
-
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) => {
|
|
27981
|
+
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, max_drawdown = false, } = WILDCARD_TARGET) => {
|
|
27362
27982
|
bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
|
|
27363
27983
|
backtest: bt$1,
|
|
27364
27984
|
breakeven,
|
|
@@ -27408,6 +28028,9 @@ class ReportUtils {
|
|
|
27408
28028
|
if (highest_profit) {
|
|
27409
28029
|
bt.highestProfitReportService.unsubscribe();
|
|
27410
28030
|
}
|
|
28031
|
+
if (max_drawdown) {
|
|
28032
|
+
bt.maxDrawdownReportService.unsubscribe();
|
|
28033
|
+
}
|
|
27411
28034
|
};
|
|
27412
28035
|
}
|
|
27413
28036
|
}
|
|
@@ -27619,6 +28242,8 @@ class BacktestReportService {
|
|
|
27619
28242
|
pnlPriceClose: data.pnl.priceClose,
|
|
27620
28243
|
totalPartials: data.signal?.totalPartials,
|
|
27621
28244
|
cost: data.signal?.cost,
|
|
28245
|
+
peakPnl: data.signal?._peak?.pnlPercentage,
|
|
28246
|
+
fallPnl: data.signal?._fall?.pnlPercentage,
|
|
27622
28247
|
}, { ...searchOptions, signalId: data.signal?.id });
|
|
27623
28248
|
}
|
|
27624
28249
|
else if (data.action === "closed") {
|
|
@@ -27651,6 +28276,8 @@ class BacktestReportService {
|
|
|
27651
28276
|
closeReason: data.closeReason,
|
|
27652
28277
|
closeTime: data.closeTimestamp,
|
|
27653
28278
|
duration: durationMin,
|
|
28279
|
+
peakPnl: data.signal?._peak?.pnlPercentage,
|
|
28280
|
+
fallPnl: data.signal?._fall?.pnlPercentage,
|
|
27654
28281
|
}, { ...searchOptions, signalId: data.signal?.id });
|
|
27655
28282
|
}
|
|
27656
28283
|
};
|
|
@@ -27813,6 +28440,8 @@ class LiveReportService {
|
|
|
27813
28440
|
pnlPriceClose: data.pnl.priceClose,
|
|
27814
28441
|
totalPartials: data.signal?.totalPartials,
|
|
27815
28442
|
cost: data.signal?.cost,
|
|
28443
|
+
peakPnl: data.signal?._peak?.pnlPercentage,
|
|
28444
|
+
fallPnl: data.signal?._fall?.pnlPercentage,
|
|
27816
28445
|
}, { ...searchOptions, signalId: data.signal?.id });
|
|
27817
28446
|
}
|
|
27818
28447
|
else if (data.action === "opened") {
|
|
@@ -27863,6 +28492,8 @@ class LiveReportService {
|
|
|
27863
28492
|
pnlPriceClose: data.pnl.priceClose,
|
|
27864
28493
|
totalPartials: data.signal?.totalPartials,
|
|
27865
28494
|
cost: data.signal?.cost,
|
|
28495
|
+
peakPnl: data.signal?._peak?.pnlPercentage,
|
|
28496
|
+
fallPnl: data.signal?._fall?.pnlPercentage,
|
|
27866
28497
|
}, { ...searchOptions, signalId: data.signal?.id });
|
|
27867
28498
|
}
|
|
27868
28499
|
else if (data.action === "closed") {
|
|
@@ -27895,6 +28526,8 @@ class LiveReportService {
|
|
|
27895
28526
|
closeReason: data.closeReason,
|
|
27896
28527
|
duration: durationMin,
|
|
27897
28528
|
closeTime: data.closeTimestamp,
|
|
28529
|
+
peakPnl: data.signal?._peak?.pnlPercentage,
|
|
28530
|
+
fallPnl: data.signal?._fall?.pnlPercentage,
|
|
27898
28531
|
}, { ...searchOptions, signalId: data.signal?.id });
|
|
27899
28532
|
}
|
|
27900
28533
|
else if (data.action === "cancelled") {
|
|
@@ -28486,6 +29119,8 @@ class HeatReportService {
|
|
|
28486
29119
|
openTime: data.signal?.pendingAt,
|
|
28487
29120
|
scheduledAt: data.signal?.scheduledAt,
|
|
28488
29121
|
closeTime: data.closeTimestamp,
|
|
29122
|
+
peakPnl: data.signal?._peak?.pnlPercentage,
|
|
29123
|
+
fallPnl: data.signal?._fall?.pnlPercentage,
|
|
28489
29124
|
}, {
|
|
28490
29125
|
symbol: data.symbol,
|
|
28491
29126
|
strategyName: data.strategyName,
|
|
@@ -29749,7 +30384,7 @@ class HighestProfitReportService {
|
|
|
29749
30384
|
* @returns Colon-separated key string for memoization
|
|
29750
30385
|
* @internal
|
|
29751
30386
|
*/
|
|
29752
|
-
const CREATE_KEY_FN$
|
|
30387
|
+
const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29753
30388
|
const parts = [symbol, strategyName, exchangeName];
|
|
29754
30389
|
if (frameName)
|
|
29755
30390
|
parts.push(frameName);
|
|
@@ -29769,7 +30404,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
29769
30404
|
* @returns Underscore-separated filename with .md extension
|
|
29770
30405
|
* @internal
|
|
29771
30406
|
*/
|
|
29772
|
-
const CREATE_FILE_NAME_FN$
|
|
30407
|
+
const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
29773
30408
|
const parts = [symbol, strategyName, exchangeName];
|
|
29774
30409
|
if (frameName) {
|
|
29775
30410
|
parts.push(frameName);
|
|
@@ -29791,7 +30426,7 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
29791
30426
|
*
|
|
29792
30427
|
* @internal
|
|
29793
30428
|
*/
|
|
29794
|
-
let ReportStorage$
|
|
30429
|
+
let ReportStorage$3 = class ReportStorage {
|
|
29795
30430
|
/**
|
|
29796
30431
|
* Creates a new ReportStorage instance.
|
|
29797
30432
|
*
|
|
@@ -29922,7 +30557,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
29922
30557
|
async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
|
|
29923
30558
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
29924
30559
|
const timestamp = getContextTimestamp();
|
|
29925
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
30560
|
+
const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
29926
30561
|
await Markdown.writeData("strategy", markdown, {
|
|
29927
30562
|
path,
|
|
29928
30563
|
file: filename,
|
|
@@ -29991,7 +30626,7 @@ class StrategyMarkdownService {
|
|
|
29991
30626
|
*
|
|
29992
30627
|
* @internal
|
|
29993
30628
|
*/
|
|
29994
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
30629
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
29995
30630
|
/**
|
|
29996
30631
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
29997
30632
|
*
|
|
@@ -30559,7 +31194,7 @@ class StrategyMarkdownService {
|
|
|
30559
31194
|
this.clear = async (payload) => {
|
|
30560
31195
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
30561
31196
|
if (payload) {
|
|
30562
|
-
const key = CREATE_KEY_FN$
|
|
31197
|
+
const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
30563
31198
|
this.getStorage.clear(key);
|
|
30564
31199
|
}
|
|
30565
31200
|
else {
|
|
@@ -30667,7 +31302,7 @@ class StrategyMarkdownService {
|
|
|
30667
31302
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
30668
31303
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
30669
31304
|
*/
|
|
30670
|
-
const CREATE_KEY_FN$
|
|
31305
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
30671
31306
|
const parts = [symbol, strategyName, exchangeName];
|
|
30672
31307
|
if (frameName)
|
|
30673
31308
|
parts.push(frameName);
|
|
@@ -30678,7 +31313,7 @@ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
30678
31313
|
* Creates a filename for the markdown report.
|
|
30679
31314
|
* Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
|
|
30680
31315
|
*/
|
|
30681
|
-
const CREATE_FILE_NAME_FN$
|
|
31316
|
+
const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
30682
31317
|
const parts = [symbol, strategyName, exchangeName];
|
|
30683
31318
|
if (frameName) {
|
|
30684
31319
|
parts.push(frameName);
|
|
@@ -30692,7 +31327,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
|
|
|
30692
31327
|
* Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
|
|
30693
31328
|
* Maintains a chronological list of signal-open and signal-close events.
|
|
30694
31329
|
*/
|
|
30695
|
-
let ReportStorage$
|
|
31330
|
+
let ReportStorage$2 = class ReportStorage {
|
|
30696
31331
|
constructor(symbol, strategyName, exchangeName, frameName, backtest) {
|
|
30697
31332
|
this.symbol = symbol;
|
|
30698
31333
|
this.strategyName = strategyName;
|
|
@@ -30826,7 +31461,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
30826
31461
|
async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
|
|
30827
31462
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
30828
31463
|
const timestamp = getContextTimestamp();
|
|
30829
|
-
const filename = CREATE_FILE_NAME_FN$
|
|
31464
|
+
const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
30830
31465
|
await Markdown.writeData("sync", markdown, {
|
|
30831
31466
|
path,
|
|
30832
31467
|
file: filename,
|
|
@@ -30860,7 +31495,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
30860
31495
|
class SyncMarkdownService {
|
|
30861
31496
|
constructor() {
|
|
30862
31497
|
this.loggerService = inject(TYPES.loggerService);
|
|
30863
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31498
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
|
|
30864
31499
|
/**
|
|
30865
31500
|
* Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
|
|
30866
31501
|
* Protected against multiple subscriptions via `singleshot` — subsequent calls
|
|
@@ -31056,7 +31691,7 @@ class SyncMarkdownService {
|
|
|
31056
31691
|
this.clear = async (payload) => {
|
|
31057
31692
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
31058
31693
|
if (payload) {
|
|
31059
|
-
const key = CREATE_KEY_FN$
|
|
31694
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31060
31695
|
this.getStorage.clear(key);
|
|
31061
31696
|
}
|
|
31062
31697
|
else {
|
|
@@ -31069,7 +31704,7 @@ class SyncMarkdownService {
|
|
|
31069
31704
|
/**
|
|
31070
31705
|
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
31071
31706
|
*/
|
|
31072
|
-
const CREATE_KEY_FN$
|
|
31707
|
+
const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31073
31708
|
const parts = [symbol, strategyName, exchangeName];
|
|
31074
31709
|
if (frameName)
|
|
31075
31710
|
parts.push(frameName);
|
|
@@ -31079,7 +31714,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
|
|
|
31079
31714
|
/**
|
|
31080
31715
|
* Creates a filename for the markdown report.
|
|
31081
31716
|
*/
|
|
31082
|
-
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
31717
|
+
const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
31083
31718
|
const parts = [symbol, strategyName, exchangeName];
|
|
31084
31719
|
if (frameName) {
|
|
31085
31720
|
parts.push(frameName);
|
|
@@ -31092,7 +31727,7 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
|
|
|
31092
31727
|
/**
|
|
31093
31728
|
* Accumulates highest profit events per symbol-strategy-exchange-frame combination.
|
|
31094
31729
|
*/
|
|
31095
|
-
class ReportStorage {
|
|
31730
|
+
let ReportStorage$1 = class ReportStorage {
|
|
31096
31731
|
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
31097
31732
|
this.symbol = symbol;
|
|
31098
31733
|
this.strategyName = strategyName;
|
|
@@ -31223,7 +31858,7 @@ class ReportStorage {
|
|
|
31223
31858
|
async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
|
|
31224
31859
|
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
31225
31860
|
const timestamp = getContextTimestamp();
|
|
31226
|
-
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
31861
|
+
const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
31227
31862
|
await Markdown.writeData("highest_profit", markdown, {
|
|
31228
31863
|
path,
|
|
31229
31864
|
file: filename,
|
|
@@ -31234,7 +31869,7 @@ class ReportStorage {
|
|
|
31234
31869
|
frameName: this.frameName,
|
|
31235
31870
|
});
|
|
31236
31871
|
}
|
|
31237
|
-
}
|
|
31872
|
+
};
|
|
31238
31873
|
/**
|
|
31239
31874
|
* Service for generating and saving highest profit markdown reports.
|
|
31240
31875
|
*
|
|
@@ -31245,7 +31880,7 @@ class ReportStorage {
|
|
|
31245
31880
|
class HighestProfitMarkdownService {
|
|
31246
31881
|
constructor() {
|
|
31247
31882
|
this.loggerService = inject(TYPES.loggerService);
|
|
31248
|
-
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
31883
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
31249
31884
|
/**
|
|
31250
31885
|
* Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
|
|
31251
31886
|
* events. Protected against multiple subscriptions via `singleshot` — subsequent
|
|
@@ -31411,7 +32046,7 @@ class HighestProfitMarkdownService {
|
|
|
31411
32046
|
this.clear = async (payload) => {
|
|
31412
32047
|
this.loggerService.log("highestProfitMarkdownService clear", { payload });
|
|
31413
32048
|
if (payload) {
|
|
31414
|
-
const key = CREATE_KEY_FN$
|
|
32049
|
+
const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31415
32050
|
this.getStorage.clear(key);
|
|
31416
32051
|
}
|
|
31417
32052
|
else {
|
|
@@ -31433,7 +32068,7 @@ const LISTEN_TIMEOUT$1 = 120000;
|
|
|
31433
32068
|
* @param backtest - Whether running in backtest mode
|
|
31434
32069
|
* @returns Unique string key for memoization
|
|
31435
32070
|
*/
|
|
31436
|
-
const CREATE_KEY_FN$
|
|
32071
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31437
32072
|
const parts = [symbol, strategyName, exchangeName];
|
|
31438
32073
|
if (frameName)
|
|
31439
32074
|
parts.push(frameName);
|
|
@@ -31476,7 +32111,7 @@ class PriceMetaService {
|
|
|
31476
32111
|
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
31477
32112
|
* Instances are cached until clear() is called.
|
|
31478
32113
|
*/
|
|
31479
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
32114
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31480
32115
|
/**
|
|
31481
32116
|
* Returns the current market price for the given symbol and context.
|
|
31482
32117
|
*
|
|
@@ -31505,10 +32140,10 @@ class PriceMetaService {
|
|
|
31505
32140
|
if (source.data) {
|
|
31506
32141
|
return source.data;
|
|
31507
32142
|
}
|
|
31508
|
-
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$
|
|
32143
|
+
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31509
32144
|
const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
31510
32145
|
if (typeof currentPrice === "symbol") {
|
|
31511
|
-
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$
|
|
32146
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31512
32147
|
}
|
|
31513
32148
|
return currentPrice;
|
|
31514
32149
|
};
|
|
@@ -31550,7 +32185,7 @@ class PriceMetaService {
|
|
|
31550
32185
|
this.getSource.clear();
|
|
31551
32186
|
return;
|
|
31552
32187
|
}
|
|
31553
|
-
const key = CREATE_KEY_FN$
|
|
32188
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31554
32189
|
this.getSource.clear(key);
|
|
31555
32190
|
};
|
|
31556
32191
|
}
|
|
@@ -31568,7 +32203,7 @@ const LISTEN_TIMEOUT = 120000;
|
|
|
31568
32203
|
* @param backtest - Whether running in backtest mode
|
|
31569
32204
|
* @returns Unique string key for memoization
|
|
31570
32205
|
*/
|
|
31571
|
-
const CREATE_KEY_FN$
|
|
32206
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
31572
32207
|
const parts = [symbol, strategyName, exchangeName];
|
|
31573
32208
|
if (frameName)
|
|
31574
32209
|
parts.push(frameName);
|
|
@@ -31611,7 +32246,7 @@ class TimeMetaService {
|
|
|
31611
32246
|
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
31612
32247
|
* Instances are cached until clear() is called.
|
|
31613
32248
|
*/
|
|
31614
|
-
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
32249
|
+
this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
|
|
31615
32250
|
/**
|
|
31616
32251
|
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
31617
32252
|
*
|
|
@@ -31639,10 +32274,10 @@ class TimeMetaService {
|
|
|
31639
32274
|
if (source.data) {
|
|
31640
32275
|
return source.data;
|
|
31641
32276
|
}
|
|
31642
|
-
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$
|
|
32277
|
+
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
31643
32278
|
const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
31644
32279
|
if (typeof timestamp === "symbol") {
|
|
31645
|
-
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$
|
|
32280
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$5(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
31646
32281
|
}
|
|
31647
32282
|
return timestamp;
|
|
31648
32283
|
};
|
|
@@ -31684,12 +32319,315 @@ class TimeMetaService {
|
|
|
31684
32319
|
this.getSource.clear();
|
|
31685
32320
|
return;
|
|
31686
32321
|
}
|
|
31687
|
-
const key = CREATE_KEY_FN$
|
|
32322
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
31688
32323
|
this.getSource.clear(key);
|
|
31689
32324
|
};
|
|
31690
32325
|
}
|
|
31691
32326
|
}
|
|
31692
32327
|
|
|
32328
|
+
const MAX_DRAWDOWN_REPORT_METHOD_NAME_SUBSCRIBE = "MaxDrawdownReportService.subscribe";
|
|
32329
|
+
const MAX_DRAWDOWN_REPORT_METHOD_NAME_UNSUBSCRIBE = "MaxDrawdownReportService.unsubscribe";
|
|
32330
|
+
const MAX_DRAWDOWN_REPORT_METHOD_NAME_TICK = "MaxDrawdownReportService.tick";
|
|
32331
|
+
/**
|
|
32332
|
+
* Service for logging max drawdown events to the JSONL report database.
|
|
32333
|
+
*
|
|
32334
|
+
* Listens to maxDrawdownSubject and writes each new drawdown record to
|
|
32335
|
+
* Report.writeData() for persistence and analytics.
|
|
32336
|
+
*/
|
|
32337
|
+
class MaxDrawdownReportService {
|
|
32338
|
+
constructor() {
|
|
32339
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
32340
|
+
/**
|
|
32341
|
+
* Handles a single `MaxDrawdownContract` event emitted by `maxDrawdownSubject`.
|
|
32342
|
+
*
|
|
32343
|
+
* Writes a JSONL record to the `"max_drawdown"` report database via
|
|
32344
|
+
* `Report.writeData`, capturing the full signal snapshot at the moment
|
|
32345
|
+
* the new drawdown record was set:
|
|
32346
|
+
* - `timestamp`, `symbol`, `strategyName`, `exchangeName`, `frameName`, `backtest`
|
|
32347
|
+
* - `signalId`, `position`, `currentPrice`
|
|
32348
|
+
* - `priceOpen`, `priceTakeProfit`, `priceStopLoss` (effective values from the signal)
|
|
32349
|
+
*
|
|
32350
|
+
* `strategyName` and signal-level fields are sourced from `data.signal`
|
|
32351
|
+
* rather than the contract root.
|
|
32352
|
+
*
|
|
32353
|
+
* @param data - `MaxDrawdownContract` payload containing `symbol`,
|
|
32354
|
+
* `signal`, `currentPrice`, `backtest`, `timestamp`, `exchangeName`,
|
|
32355
|
+
* `frameName`
|
|
32356
|
+
*/
|
|
32357
|
+
this.tick = async (data) => {
|
|
32358
|
+
this.loggerService.log(MAX_DRAWDOWN_REPORT_METHOD_NAME_TICK, { data });
|
|
32359
|
+
await Report.writeData("max_drawdown", {
|
|
32360
|
+
timestamp: data.timestamp,
|
|
32361
|
+
symbol: data.symbol,
|
|
32362
|
+
strategyName: data.signal.strategyName,
|
|
32363
|
+
exchangeName: data.exchangeName,
|
|
32364
|
+
frameName: data.frameName,
|
|
32365
|
+
backtest: data.backtest,
|
|
32366
|
+
signalId: data.signal.id,
|
|
32367
|
+
position: data.signal.position,
|
|
32368
|
+
currentPrice: data.currentPrice,
|
|
32369
|
+
priceOpen: data.signal.priceOpen,
|
|
32370
|
+
priceTakeProfit: data.signal.priceTakeProfit,
|
|
32371
|
+
priceStopLoss: data.signal.priceStopLoss,
|
|
32372
|
+
}, {
|
|
32373
|
+
symbol: data.symbol,
|
|
32374
|
+
strategyName: data.signal.strategyName,
|
|
32375
|
+
exchangeName: data.exchangeName,
|
|
32376
|
+
frameName: data.frameName,
|
|
32377
|
+
signalId: data.signal.id,
|
|
32378
|
+
walkerName: "",
|
|
32379
|
+
});
|
|
32380
|
+
};
|
|
32381
|
+
/**
|
|
32382
|
+
* Subscribes to `maxDrawdownSubject` to start persisting drawdown records.
|
|
32383
|
+
* Protected against multiple subscriptions via `singleshot` — subsequent
|
|
32384
|
+
* calls return the same unsubscribe function without re-subscribing.
|
|
32385
|
+
*
|
|
32386
|
+
* The returned unsubscribe function clears the `singleshot` state and
|
|
32387
|
+
* detaches from `maxDrawdownSubject`.
|
|
32388
|
+
*
|
|
32389
|
+
* @returns Unsubscribe function; calling it tears down the subscription
|
|
32390
|
+
*/
|
|
32391
|
+
this.subscribe = singleshot(() => {
|
|
32392
|
+
this.loggerService.log(MAX_DRAWDOWN_REPORT_METHOD_NAME_SUBSCRIBE);
|
|
32393
|
+
const unsub = maxDrawdownSubject.subscribe(this.tick);
|
|
32394
|
+
return () => {
|
|
32395
|
+
this.subscribe.clear();
|
|
32396
|
+
unsub();
|
|
32397
|
+
};
|
|
32398
|
+
});
|
|
32399
|
+
/**
|
|
32400
|
+
* Detaches from `maxDrawdownSubject`, stopping further JSONL writes.
|
|
32401
|
+
*
|
|
32402
|
+
* Calls the unsubscribe closure returned by `subscribe()`.
|
|
32403
|
+
* If `subscribe()` was never called, does nothing.
|
|
32404
|
+
*/
|
|
32405
|
+
this.unsubscribe = async () => {
|
|
32406
|
+
this.loggerService.log(MAX_DRAWDOWN_REPORT_METHOD_NAME_UNSUBSCRIBE);
|
|
32407
|
+
if (this.subscribe.hasValue()) {
|
|
32408
|
+
const lastSubscription = this.subscribe();
|
|
32409
|
+
lastSubscription();
|
|
32410
|
+
}
|
|
32411
|
+
};
|
|
32412
|
+
}
|
|
32413
|
+
}
|
|
32414
|
+
|
|
32415
|
+
/**
|
|
32416
|
+
* Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
|
|
32417
|
+
*/
|
|
32418
|
+
const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
32419
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
32420
|
+
if (frameName)
|
|
32421
|
+
parts.push(frameName);
|
|
32422
|
+
parts.push(backtest ? "backtest" : "live");
|
|
32423
|
+
return parts.join(":");
|
|
32424
|
+
};
|
|
32425
|
+
/**
|
|
32426
|
+
* Creates a filename for the markdown report.
|
|
32427
|
+
*/
|
|
32428
|
+
const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
|
|
32429
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
32430
|
+
if (frameName) {
|
|
32431
|
+
parts.push(frameName);
|
|
32432
|
+
parts.push("backtest");
|
|
32433
|
+
}
|
|
32434
|
+
else
|
|
32435
|
+
parts.push("live");
|
|
32436
|
+
return `${parts.join("_")}-${timestamp}.md`;
|
|
32437
|
+
};
|
|
32438
|
+
/**
|
|
32439
|
+
* Accumulates max drawdown events per symbol-strategy-exchange-frame combination.
|
|
32440
|
+
*/
|
|
32441
|
+
class ReportStorage {
|
|
32442
|
+
constructor(symbol, strategyName, exchangeName, frameName) {
|
|
32443
|
+
this.symbol = symbol;
|
|
32444
|
+
this.strategyName = strategyName;
|
|
32445
|
+
this.exchangeName = exchangeName;
|
|
32446
|
+
this.frameName = frameName;
|
|
32447
|
+
this._eventList = [];
|
|
32448
|
+
}
|
|
32449
|
+
/**
|
|
32450
|
+
* Constructs a `MaxDrawdownEvent` from the given signal snapshot and
|
|
32451
|
+
* prepends it to the internal queue (most recent first).
|
|
32452
|
+
*
|
|
32453
|
+
* Once the queue exceeds `GLOBAL_CONFIG.CC_MAX_MAX_DRAWDOWN_MARKDOWN_ROWS`
|
|
32454
|
+
* entries, the oldest entry is dropped from the tail.
|
|
32455
|
+
*/
|
|
32456
|
+
addEvent(data, currentPrice, backtest, timestamp) {
|
|
32457
|
+
this._eventList.unshift({
|
|
32458
|
+
timestamp,
|
|
32459
|
+
symbol: data.symbol,
|
|
32460
|
+
strategyName: data.strategyName,
|
|
32461
|
+
signalId: data.id,
|
|
32462
|
+
position: data.position,
|
|
32463
|
+
pnl: data.pnl,
|
|
32464
|
+
currentPrice,
|
|
32465
|
+
priceOpen: data.priceOpen,
|
|
32466
|
+
priceTakeProfit: data.priceTakeProfit,
|
|
32467
|
+
priceStopLoss: data.priceStopLoss,
|
|
32468
|
+
backtest,
|
|
32469
|
+
});
|
|
32470
|
+
if (this._eventList.length > GLOBAL_CONFIG.CC_MAX_MAX_DRAWDOWN_MARKDOWN_ROWS) {
|
|
32471
|
+
this._eventList.pop();
|
|
32472
|
+
}
|
|
32473
|
+
}
|
|
32474
|
+
/**
|
|
32475
|
+
* Returns the accumulated event list with a total count.
|
|
32476
|
+
*/
|
|
32477
|
+
async getData() {
|
|
32478
|
+
return {
|
|
32479
|
+
eventList: this._eventList,
|
|
32480
|
+
totalEvents: this._eventList.length,
|
|
32481
|
+
};
|
|
32482
|
+
}
|
|
32483
|
+
/**
|
|
32484
|
+
* Renders a markdown max drawdown report for this storage instance.
|
|
32485
|
+
*/
|
|
32486
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.max_drawdown_columns) {
|
|
32487
|
+
const stats = await this.getData();
|
|
32488
|
+
if (stats.totalEvents === 0) {
|
|
32489
|
+
return [
|
|
32490
|
+
`# Max Drawdown Report: ${symbol}:${strategyName}`,
|
|
32491
|
+
"",
|
|
32492
|
+
"No max drawdown events recorded yet.",
|
|
32493
|
+
].join("\n");
|
|
32494
|
+
}
|
|
32495
|
+
const visibleColumns = [];
|
|
32496
|
+
for (const col of columns) {
|
|
32497
|
+
if (await col.isVisible()) {
|
|
32498
|
+
visibleColumns.push(col);
|
|
32499
|
+
}
|
|
32500
|
+
}
|
|
32501
|
+
const header = visibleColumns.map((col) => col.label);
|
|
32502
|
+
const separator = visibleColumns.map(() => "---");
|
|
32503
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
32504
|
+
const tableData = [header, separator, ...rows];
|
|
32505
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
32506
|
+
return [
|
|
32507
|
+
`# Max Drawdown Report: ${symbol}:${strategyName}`,
|
|
32508
|
+
"",
|
|
32509
|
+
table,
|
|
32510
|
+
"",
|
|
32511
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
32512
|
+
].join("\n");
|
|
32513
|
+
}
|
|
32514
|
+
/**
|
|
32515
|
+
* Generates the markdown report and persists it via `Markdown.writeData`.
|
|
32516
|
+
*/
|
|
32517
|
+
async dump(symbol, strategyName, path = "./dump/max_drawdown", columns = COLUMN_CONFIG.max_drawdown_columns) {
|
|
32518
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
32519
|
+
const timestamp = getContextTimestamp();
|
|
32520
|
+
const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
|
|
32521
|
+
await Markdown.writeData("max_drawdown", markdown, {
|
|
32522
|
+
path,
|
|
32523
|
+
file: filename,
|
|
32524
|
+
symbol: this.symbol,
|
|
32525
|
+
signalId: "",
|
|
32526
|
+
strategyName: this.strategyName,
|
|
32527
|
+
exchangeName: this.exchangeName,
|
|
32528
|
+
frameName: this.frameName,
|
|
32529
|
+
});
|
|
32530
|
+
}
|
|
32531
|
+
}
|
|
32532
|
+
/**
|
|
32533
|
+
* Service for generating and saving max drawdown markdown reports.
|
|
32534
|
+
*
|
|
32535
|
+
* Listens to maxDrawdownSubject and accumulates events per
|
|
32536
|
+
* symbol-strategy-exchange-frame combination. Provides getData(),
|
|
32537
|
+
* getReport(), and dump() methods matching the HighestProfit pattern.
|
|
32538
|
+
*/
|
|
32539
|
+
class MaxDrawdownMarkdownService {
|
|
32540
|
+
constructor() {
|
|
32541
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
32542
|
+
this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
32543
|
+
/**
|
|
32544
|
+
* Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
|
|
32545
|
+
* events. Protected against multiple subscriptions via `singleshot`.
|
|
32546
|
+
*
|
|
32547
|
+
* @returns Unsubscribe function; calling it tears down the subscription and
|
|
32548
|
+
* clears all accumulated data
|
|
32549
|
+
*/
|
|
32550
|
+
this.subscribe = singleshot(() => {
|
|
32551
|
+
this.loggerService.log("maxDrawdownMarkdownService init");
|
|
32552
|
+
const unsub = maxDrawdownSubject.subscribe(this.tick);
|
|
32553
|
+
return () => {
|
|
32554
|
+
this.subscribe.clear();
|
|
32555
|
+
this.clear();
|
|
32556
|
+
unsub();
|
|
32557
|
+
};
|
|
32558
|
+
});
|
|
32559
|
+
/**
|
|
32560
|
+
* Detaches from `maxDrawdownSubject` and clears all accumulated data.
|
|
32561
|
+
*
|
|
32562
|
+
* If `subscribe()` was never called, does nothing.
|
|
32563
|
+
*/
|
|
32564
|
+
this.unsubscribe = async () => {
|
|
32565
|
+
this.loggerService.log("maxDrawdownMarkdownService unsubscribe");
|
|
32566
|
+
if (this.subscribe.hasValue()) {
|
|
32567
|
+
const lastSubscription = this.subscribe();
|
|
32568
|
+
lastSubscription();
|
|
32569
|
+
}
|
|
32570
|
+
};
|
|
32571
|
+
/**
|
|
32572
|
+
* Handles a single `MaxDrawdownContract` event emitted by `maxDrawdownSubject`.
|
|
32573
|
+
*/
|
|
32574
|
+
this.tick = async (data) => {
|
|
32575
|
+
this.loggerService.log("maxDrawdownMarkdownService tick", { data });
|
|
32576
|
+
const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
|
|
32577
|
+
storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
|
|
32578
|
+
};
|
|
32579
|
+
/**
|
|
32580
|
+
* Returns accumulated max drawdown statistics for the given context.
|
|
32581
|
+
*/
|
|
32582
|
+
this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
32583
|
+
this.loggerService.log("maxDrawdownMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
32584
|
+
if (!this.subscribe.hasValue()) {
|
|
32585
|
+
throw new Error("MaxDrawdownMarkdownService not initialized. Call subscribe() before getting data.");
|
|
32586
|
+
}
|
|
32587
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
32588
|
+
return storage.getData();
|
|
32589
|
+
};
|
|
32590
|
+
/**
|
|
32591
|
+
* Generates a markdown max drawdown report for the given context.
|
|
32592
|
+
*/
|
|
32593
|
+
this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.max_drawdown_columns) => {
|
|
32594
|
+
this.loggerService.log("maxDrawdownMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
|
|
32595
|
+
if (!this.subscribe.hasValue()) {
|
|
32596
|
+
throw new Error("MaxDrawdownMarkdownService not initialized. Call subscribe() before generating reports.");
|
|
32597
|
+
}
|
|
32598
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
32599
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
32600
|
+
};
|
|
32601
|
+
/**
|
|
32602
|
+
* Generates the max drawdown report and writes it to disk.
|
|
32603
|
+
*/
|
|
32604
|
+
this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/max_drawdown", columns = COLUMN_CONFIG.max_drawdown_columns) => {
|
|
32605
|
+
this.loggerService.log("maxDrawdownMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
|
|
32606
|
+
if (!this.subscribe.hasValue()) {
|
|
32607
|
+
throw new Error("MaxDrawdownMarkdownService not initialized. Call subscribe() before dumping reports.");
|
|
32608
|
+
}
|
|
32609
|
+
const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
|
|
32610
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
32611
|
+
};
|
|
32612
|
+
/**
|
|
32613
|
+
* Evicts memoized `ReportStorage` instances, releasing all accumulated event data.
|
|
32614
|
+
*
|
|
32615
|
+
* - With `payload` — clears only the storage bucket for that combination.
|
|
32616
|
+
* - Without `payload` — clears **all** storage buckets.
|
|
32617
|
+
*/
|
|
32618
|
+
this.clear = async (payload) => {
|
|
32619
|
+
this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
|
|
32620
|
+
if (payload) {
|
|
32621
|
+
const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
32622
|
+
this.getStorage.clear(key);
|
|
32623
|
+
}
|
|
32624
|
+
else {
|
|
32625
|
+
this.getStorage.clear();
|
|
32626
|
+
}
|
|
32627
|
+
};
|
|
32628
|
+
}
|
|
32629
|
+
}
|
|
32630
|
+
|
|
31693
32631
|
{
|
|
31694
32632
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
31695
32633
|
}
|
|
@@ -31760,6 +32698,7 @@ class TimeMetaService {
|
|
|
31760
32698
|
provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
|
|
31761
32699
|
provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
|
|
31762
32700
|
provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
|
|
32701
|
+
provide(TYPES.maxDrawdownMarkdownService, () => new MaxDrawdownMarkdownService());
|
|
31763
32702
|
}
|
|
31764
32703
|
{
|
|
31765
32704
|
provide(TYPES.backtestReportService, () => new BacktestReportService());
|
|
@@ -31774,6 +32713,7 @@ class TimeMetaService {
|
|
|
31774
32713
|
provide(TYPES.strategyReportService, () => new StrategyReportService());
|
|
31775
32714
|
provide(TYPES.syncReportService, () => new SyncReportService());
|
|
31776
32715
|
provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
|
|
32716
|
+
provide(TYPES.maxDrawdownReportService, () => new MaxDrawdownReportService());
|
|
31777
32717
|
}
|
|
31778
32718
|
{
|
|
31779
32719
|
provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
|
|
@@ -31857,6 +32797,7 @@ const markdownServices = {
|
|
|
31857
32797
|
strategyMarkdownService: inject(TYPES.strategyMarkdownService),
|
|
31858
32798
|
syncMarkdownService: inject(TYPES.syncMarkdownService),
|
|
31859
32799
|
highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
|
|
32800
|
+
maxDrawdownMarkdownService: inject(TYPES.maxDrawdownMarkdownService),
|
|
31860
32801
|
};
|
|
31861
32802
|
const reportServices = {
|
|
31862
32803
|
backtestReportService: inject(TYPES.backtestReportService),
|
|
@@ -31871,6 +32812,7 @@ const reportServices = {
|
|
|
31871
32812
|
strategyReportService: inject(TYPES.strategyReportService),
|
|
31872
32813
|
syncReportService: inject(TYPES.syncReportService),
|
|
31873
32814
|
highestProfitReportService: inject(TYPES.highestProfitReportService),
|
|
32815
|
+
maxDrawdownReportService: inject(TYPES.maxDrawdownReportService),
|
|
31874
32816
|
};
|
|
31875
32817
|
const validationServices = {
|
|
31876
32818
|
exchangeValidationService: inject(TYPES.exchangeValidationService),
|
|
@@ -34798,6 +35740,12 @@ const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHig
|
|
|
34798
35740
|
const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
|
|
34799
35741
|
const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
|
|
34800
35742
|
const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
|
|
35743
|
+
const GET_POSITION_HIGHEST_PROFIT_MINUTES_METHOD_NAME = "strategy.getPositionHighestProfitMinutes";
|
|
35744
|
+
const GET_POSITION_MAX_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionMaxDrawdownMinutes";
|
|
35745
|
+
const GET_POSITION_MAX_DRAWDOWN_PRICE_METHOD_NAME = "strategy.getPositionMaxDrawdownPrice";
|
|
35746
|
+
const GET_POSITION_MAX_DRAWDOWN_TIMESTAMP_METHOD_NAME = "strategy.getPositionMaxDrawdownTimestamp";
|
|
35747
|
+
const GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlPercentage";
|
|
35748
|
+
const GET_POSITION_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlCost";
|
|
34801
35749
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
34802
35750
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
34803
35751
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -36257,6 +37205,181 @@ async function getPositionDrawdownMinutes(symbol) {
|
|
|
36257
37205
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
36258
37206
|
return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36259
37207
|
}
|
|
37208
|
+
/**
|
|
37209
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
37210
|
+
*
|
|
37211
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
37212
|
+
* pulling back from its peak profit level.
|
|
37213
|
+
* Zero when called at the exact moment the peak was set.
|
|
37214
|
+
*
|
|
37215
|
+
* Returns null if no pending signal exists.
|
|
37216
|
+
*
|
|
37217
|
+
* @param symbol - Trading pair symbol
|
|
37218
|
+
* @returns Promise resolving to minutes since last profit peak or null
|
|
37219
|
+
*
|
|
37220
|
+
* @example
|
|
37221
|
+
* ```typescript
|
|
37222
|
+
* import { getPositionHighestProfitMinutes } from "backtest-kit";
|
|
37223
|
+
*
|
|
37224
|
+
* const minutes = await getPositionHighestProfitMinutes("BTCUSDT");
|
|
37225
|
+
* // e.g. 30 (30 minutes since the highest profit price)
|
|
37226
|
+
* ```
|
|
37227
|
+
*/
|
|
37228
|
+
async function getPositionHighestProfitMinutes(symbol) {
|
|
37229
|
+
bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_MINUTES_METHOD_NAME, { symbol });
|
|
37230
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37231
|
+
throw new Error("getPositionHighestProfitMinutes requires an execution context");
|
|
37232
|
+
}
|
|
37233
|
+
if (!MethodContextService.hasContext()) {
|
|
37234
|
+
throw new Error("getPositionHighestProfitMinutes requires a method context");
|
|
37235
|
+
}
|
|
37236
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
37237
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
37238
|
+
return await bt.strategyCoreService.getPositionHighestProfitMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37239
|
+
}
|
|
37240
|
+
/**
|
|
37241
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
37242
|
+
*
|
|
37243
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
37244
|
+
* Zero when called at the exact moment the trough was set.
|
|
37245
|
+
*
|
|
37246
|
+
* Returns null if no pending signal exists.
|
|
37247
|
+
*
|
|
37248
|
+
* @param symbol - Trading pair symbol
|
|
37249
|
+
* @returns Promise resolving to minutes since last drawdown trough or null
|
|
37250
|
+
*
|
|
37251
|
+
* @example
|
|
37252
|
+
* ```typescript
|
|
37253
|
+
* import { getPositionMaxDrawdownMinutes } from "backtest-kit";
|
|
37254
|
+
*
|
|
37255
|
+
* const minutes = await getPositionMaxDrawdownMinutes("BTCUSDT");
|
|
37256
|
+
* // e.g. 15 (15 minutes since the worst loss price)
|
|
37257
|
+
* ```
|
|
37258
|
+
*/
|
|
37259
|
+
async function getPositionMaxDrawdownMinutes(symbol) {
|
|
37260
|
+
bt.loggerService.info(GET_POSITION_MAX_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
|
|
37261
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37262
|
+
throw new Error("getPositionMaxDrawdownMinutes requires an execution context");
|
|
37263
|
+
}
|
|
37264
|
+
if (!MethodContextService.hasContext()) {
|
|
37265
|
+
throw new Error("getPositionMaxDrawdownMinutes requires a method context");
|
|
37266
|
+
}
|
|
37267
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
37268
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
37269
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37270
|
+
}
|
|
37271
|
+
/**
|
|
37272
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
37273
|
+
*
|
|
37274
|
+
* Returns null if no pending signal exists.
|
|
37275
|
+
*
|
|
37276
|
+
* @param symbol - Trading pair symbol
|
|
37277
|
+
* @returns Promise resolving to price or null
|
|
37278
|
+
*
|
|
37279
|
+
* @example
|
|
37280
|
+
* ```typescript
|
|
37281
|
+
* import { getPositionMaxDrawdownPrice } from "backtest-kit";
|
|
37282
|
+
*
|
|
37283
|
+
* const price = await getPositionMaxDrawdownPrice("BTCUSDT");
|
|
37284
|
+
* // e.g. 41000 (lowest price seen for a LONG position)
|
|
37285
|
+
* ```
|
|
37286
|
+
*/
|
|
37287
|
+
async function getPositionMaxDrawdownPrice(symbol) {
|
|
37288
|
+
bt.loggerService.info(GET_POSITION_MAX_DRAWDOWN_PRICE_METHOD_NAME, { symbol });
|
|
37289
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37290
|
+
throw new Error("getPositionMaxDrawdownPrice requires an execution context");
|
|
37291
|
+
}
|
|
37292
|
+
if (!MethodContextService.hasContext()) {
|
|
37293
|
+
throw new Error("getPositionMaxDrawdownPrice requires a method context");
|
|
37294
|
+
}
|
|
37295
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
37296
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
37297
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37298
|
+
}
|
|
37299
|
+
/**
|
|
37300
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
37301
|
+
*
|
|
37302
|
+
* Returns null if no pending signal exists.
|
|
37303
|
+
*
|
|
37304
|
+
* @param symbol - Trading pair symbol
|
|
37305
|
+
* @returns Promise resolving to timestamp in milliseconds or null
|
|
37306
|
+
*
|
|
37307
|
+
* @example
|
|
37308
|
+
* ```typescript
|
|
37309
|
+
* import { getPositionMaxDrawdownTimestamp } from "backtest-kit";
|
|
37310
|
+
*
|
|
37311
|
+
* const ts = await getPositionMaxDrawdownTimestamp("BTCUSDT");
|
|
37312
|
+
* // e.g. 1700000000000
|
|
37313
|
+
* ```
|
|
37314
|
+
*/
|
|
37315
|
+
async function getPositionMaxDrawdownTimestamp(symbol) {
|
|
37316
|
+
bt.loggerService.info(GET_POSITION_MAX_DRAWDOWN_TIMESTAMP_METHOD_NAME, { symbol });
|
|
37317
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37318
|
+
throw new Error("getPositionMaxDrawdownTimestamp requires an execution context");
|
|
37319
|
+
}
|
|
37320
|
+
if (!MethodContextService.hasContext()) {
|
|
37321
|
+
throw new Error("getPositionMaxDrawdownTimestamp requires a method context");
|
|
37322
|
+
}
|
|
37323
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
37324
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
37325
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37326
|
+
}
|
|
37327
|
+
/**
|
|
37328
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
37329
|
+
*
|
|
37330
|
+
* Returns null if no pending signal exists.
|
|
37331
|
+
*
|
|
37332
|
+
* @param symbol - Trading pair symbol
|
|
37333
|
+
* @returns Promise resolving to PnL percentage or null
|
|
37334
|
+
*
|
|
37335
|
+
* @example
|
|
37336
|
+
* ```typescript
|
|
37337
|
+
* import { getPositionMaxDrawdownPnlPercentage } from "backtest-kit";
|
|
37338
|
+
*
|
|
37339
|
+
* const pnl = await getPositionMaxDrawdownPnlPercentage("BTCUSDT");
|
|
37340
|
+
* // e.g. -5.2 (deepest PnL percentage reached)
|
|
37341
|
+
* ```
|
|
37342
|
+
*/
|
|
37343
|
+
async function getPositionMaxDrawdownPnlPercentage(symbol) {
|
|
37344
|
+
bt.loggerService.info(GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
37345
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37346
|
+
throw new Error("getPositionMaxDrawdownPnlPercentage requires an execution context");
|
|
37347
|
+
}
|
|
37348
|
+
if (!MethodContextService.hasContext()) {
|
|
37349
|
+
throw new Error("getPositionMaxDrawdownPnlPercentage requires a method context");
|
|
37350
|
+
}
|
|
37351
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
37352
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
37353
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37354
|
+
}
|
|
37355
|
+
/**
|
|
37356
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
37357
|
+
*
|
|
37358
|
+
* Returns null if no pending signal exists.
|
|
37359
|
+
*
|
|
37360
|
+
* @param symbol - Trading pair symbol
|
|
37361
|
+
* @returns Promise resolving to PnL cost or null
|
|
37362
|
+
*
|
|
37363
|
+
* @example
|
|
37364
|
+
* ```typescript
|
|
37365
|
+
* import { getPositionMaxDrawdownPnlCost } from "backtest-kit";
|
|
37366
|
+
*
|
|
37367
|
+
* const cost = await getPositionMaxDrawdownPnlCost("BTCUSDT");
|
|
37368
|
+
* // e.g. -52 (deepest PnL in quote currency)
|
|
37369
|
+
* ```
|
|
37370
|
+
*/
|
|
37371
|
+
async function getPositionMaxDrawdownPnlCost(symbol) {
|
|
37372
|
+
bt.loggerService.info(GET_POSITION_MAX_DRAWDOWN_PNL_COST_METHOD_NAME, { symbol });
|
|
37373
|
+
if (!ExecutionContextService.hasContext()) {
|
|
37374
|
+
throw new Error("getPositionMaxDrawdownPnlCost requires an execution context");
|
|
37375
|
+
}
|
|
37376
|
+
if (!MethodContextService.hasContext()) {
|
|
37377
|
+
throw new Error("getPositionMaxDrawdownPnlCost requires a method context");
|
|
37378
|
+
}
|
|
37379
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
37380
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
37381
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
37382
|
+
}
|
|
36260
37383
|
/**
|
|
36261
37384
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36262
37385
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -36498,6 +37621,8 @@ const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
|
|
|
36498
37621
|
const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
|
|
36499
37622
|
const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
|
|
36500
37623
|
const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
|
|
37624
|
+
const LISTEN_MAX_DRAWDOWN_METHOD_NAME = "event.listenMaxDrawdown";
|
|
37625
|
+
const LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME = "event.listenMaxDrawdownOnce";
|
|
36501
37626
|
/**
|
|
36502
37627
|
* Subscribes to all signal events with queued async processing.
|
|
36503
37628
|
*
|
|
@@ -37848,6 +38973,47 @@ function listenHighestProfitOnce(filterFn, fn) {
|
|
|
37848
38973
|
};
|
|
37849
38974
|
return disposeFn = listenHighestProfit(wrappedFn);
|
|
37850
38975
|
}
|
|
38976
|
+
/**
|
|
38977
|
+
* Subscribes to max drawdown events with queued async processing.
|
|
38978
|
+
* Emits when a signal reaches a new maximum drawdown level during its lifecycle.
|
|
38979
|
+
* Events are processed sequentially in order received, even if callback is async.
|
|
38980
|
+
* Uses queued wrapper to prevent concurrent execution of the callback.
|
|
38981
|
+
* Useful for tracking drawdown milestones and implementing dynamic risk management logic.
|
|
38982
|
+
* @param fn - Callback function to handle max drawdown events
|
|
38983
|
+
* @return Unsubscribe function to stop listening to events
|
|
38984
|
+
*/
|
|
38985
|
+
function listenMaxDrawdown(fn) {
|
|
38986
|
+
bt.loggerService.log(LISTEN_MAX_DRAWDOWN_METHOD_NAME);
|
|
38987
|
+
const wrappedFn = async (event) => {
|
|
38988
|
+
if (await bt.strategyCoreService.hasPendingSignal(event.backtest, event.symbol, {
|
|
38989
|
+
strategyName: event.strategyName,
|
|
38990
|
+
exchangeName: event.exchangeName,
|
|
38991
|
+
frameName: event.frameName,
|
|
38992
|
+
})) {
|
|
38993
|
+
await fn(event);
|
|
38994
|
+
}
|
|
38995
|
+
};
|
|
38996
|
+
return maxDrawdownSubject.subscribe(queued(wrappedFn));
|
|
38997
|
+
}
|
|
38998
|
+
/**
|
|
38999
|
+
* Subscribes to filtered max drawdown events with one-time execution.
|
|
39000
|
+
* Listens for events matching the filter predicate, then executes callback once
|
|
39001
|
+
* and automatically unsubscribes. Useful for waiting for specific drawdown conditions.
|
|
39002
|
+
* @param filterFn - Predicate to filter which events trigger the callback
|
|
39003
|
+
* @param fn - Callback function to handle the filtered event (called only once)
|
|
39004
|
+
* @return Unsubscribe function to cancel the listener before it fires
|
|
39005
|
+
*/
|
|
39006
|
+
function listenMaxDrawdownOnce(filterFn, fn) {
|
|
39007
|
+
bt.loggerService.log(LISTEN_MAX_DRAWDOWN_ONCE_METHOD_NAME);
|
|
39008
|
+
let disposeFn;
|
|
39009
|
+
const wrappedFn = async (event) => {
|
|
39010
|
+
if (filterFn(event)) {
|
|
39011
|
+
await fn(event);
|
|
39012
|
+
disposeFn && disposeFn();
|
|
39013
|
+
}
|
|
39014
|
+
};
|
|
39015
|
+
return disposeFn = listenMaxDrawdown(wrappedFn);
|
|
39016
|
+
}
|
|
37851
39017
|
|
|
37852
39018
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
37853
39019
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
@@ -37877,6 +39043,12 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.
|
|
|
37877
39043
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
|
|
37878
39044
|
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
|
|
37879
39045
|
const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
|
|
39046
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "BacktestUtils.getPositionHighestProfitMinutes";
|
|
39047
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "BacktestUtils.getPositionMaxDrawdownMinutes";
|
|
39048
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "BacktestUtils.getPositionMaxDrawdownPrice";
|
|
39049
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "BacktestUtils.getPositionMaxDrawdownTimestamp";
|
|
39050
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionMaxDrawdownPnlPercentage";
|
|
39051
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionMaxDrawdownPnlCost";
|
|
37880
39052
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
37881
39053
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
37882
39054
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -38977,6 +40149,175 @@ class BacktestUtils {
|
|
|
38977
40149
|
}
|
|
38978
40150
|
return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
|
|
38979
40151
|
};
|
|
40152
|
+
/**
|
|
40153
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
40154
|
+
*
|
|
40155
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
40156
|
+
* pulling back from its peak profit level.
|
|
40157
|
+
* Zero when called at the exact moment the peak was set.
|
|
40158
|
+
*
|
|
40159
|
+
* Returns null if no pending signal exists.
|
|
40160
|
+
*
|
|
40161
|
+
* @param symbol - Trading pair symbol
|
|
40162
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40163
|
+
* @returns Minutes since last profit peak, or null if no active position
|
|
40164
|
+
*/
|
|
40165
|
+
this.getPositionHighestProfitMinutes = async (symbol, context) => {
|
|
40166
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, {
|
|
40167
|
+
symbol,
|
|
40168
|
+
context,
|
|
40169
|
+
});
|
|
40170
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
40171
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
40172
|
+
{
|
|
40173
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
40174
|
+
riskName &&
|
|
40175
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
40176
|
+
riskList &&
|
|
40177
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
40178
|
+
actions &&
|
|
40179
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
40180
|
+
}
|
|
40181
|
+
return await bt.strategyCoreService.getPositionHighestProfitMinutes(true, symbol, context);
|
|
40182
|
+
};
|
|
40183
|
+
/**
|
|
40184
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
40185
|
+
*
|
|
40186
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
40187
|
+
* Zero when called at the exact moment the trough was set.
|
|
40188
|
+
*
|
|
40189
|
+
* Returns null if no pending signal exists.
|
|
40190
|
+
*
|
|
40191
|
+
* @param symbol - Trading pair symbol
|
|
40192
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40193
|
+
* @returns Minutes since last drawdown trough, or null if no active position
|
|
40194
|
+
*/
|
|
40195
|
+
this.getPositionMaxDrawdownMinutes = async (symbol, context) => {
|
|
40196
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, {
|
|
40197
|
+
symbol,
|
|
40198
|
+
context,
|
|
40199
|
+
});
|
|
40200
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
40201
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
40202
|
+
{
|
|
40203
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
40204
|
+
riskName &&
|
|
40205
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
40206
|
+
riskList &&
|
|
40207
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
40208
|
+
actions &&
|
|
40209
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
40210
|
+
}
|
|
40211
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownMinutes(true, symbol, context);
|
|
40212
|
+
};
|
|
40213
|
+
/**
|
|
40214
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
40215
|
+
*
|
|
40216
|
+
* Returns null if no pending signal exists.
|
|
40217
|
+
*
|
|
40218
|
+
* @param symbol - Trading pair symbol
|
|
40219
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40220
|
+
* @returns price or null if no active position
|
|
40221
|
+
*/
|
|
40222
|
+
this.getPositionMaxDrawdownPrice = async (symbol, context) => {
|
|
40223
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, {
|
|
40224
|
+
symbol,
|
|
40225
|
+
context,
|
|
40226
|
+
});
|
|
40227
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
40228
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
40229
|
+
{
|
|
40230
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
40231
|
+
riskName &&
|
|
40232
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
40233
|
+
riskList &&
|
|
40234
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
40235
|
+
actions &&
|
|
40236
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
40237
|
+
}
|
|
40238
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPrice(true, symbol, context);
|
|
40239
|
+
};
|
|
40240
|
+
/**
|
|
40241
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
40242
|
+
*
|
|
40243
|
+
* Returns null if no pending signal exists.
|
|
40244
|
+
*
|
|
40245
|
+
* @param symbol - Trading pair symbol
|
|
40246
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40247
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
40248
|
+
*/
|
|
40249
|
+
this.getPositionMaxDrawdownTimestamp = async (symbol, context) => {
|
|
40250
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, {
|
|
40251
|
+
symbol,
|
|
40252
|
+
context,
|
|
40253
|
+
});
|
|
40254
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
40255
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
40256
|
+
{
|
|
40257
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
40258
|
+
riskName &&
|
|
40259
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
40260
|
+
riskList &&
|
|
40261
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
40262
|
+
actions &&
|
|
40263
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
40264
|
+
}
|
|
40265
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownTimestamp(true, symbol, context);
|
|
40266
|
+
};
|
|
40267
|
+
/**
|
|
40268
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
40269
|
+
*
|
|
40270
|
+
* Returns null if no pending signal exists.
|
|
40271
|
+
*
|
|
40272
|
+
* @param symbol - Trading pair symbol
|
|
40273
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40274
|
+
* @returns PnL percentage or null if no active position
|
|
40275
|
+
*/
|
|
40276
|
+
this.getPositionMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
40277
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
40278
|
+
symbol,
|
|
40279
|
+
context,
|
|
40280
|
+
});
|
|
40281
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
40282
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
40283
|
+
{
|
|
40284
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
40285
|
+
riskName &&
|
|
40286
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
40287
|
+
riskList &&
|
|
40288
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
40289
|
+
actions &&
|
|
40290
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
40291
|
+
}
|
|
40292
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPnlPercentage(true, symbol, context);
|
|
40293
|
+
};
|
|
40294
|
+
/**
|
|
40295
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
40296
|
+
*
|
|
40297
|
+
* Returns null if no pending signal exists.
|
|
40298
|
+
*
|
|
40299
|
+
* @param symbol - Trading pair symbol
|
|
40300
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
40301
|
+
* @returns PnL cost or null if no active position
|
|
40302
|
+
*/
|
|
40303
|
+
this.getPositionMaxDrawdownPnlCost = async (symbol, context) => {
|
|
40304
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, {
|
|
40305
|
+
symbol,
|
|
40306
|
+
context,
|
|
40307
|
+
});
|
|
40308
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
40309
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
40310
|
+
{
|
|
40311
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
40312
|
+
riskName &&
|
|
40313
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
40314
|
+
riskList &&
|
|
40315
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
40316
|
+
actions &&
|
|
40317
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
40318
|
+
}
|
|
40319
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPnlCost(true, symbol, context);
|
|
40320
|
+
};
|
|
38980
40321
|
/**
|
|
38981
40322
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
38982
40323
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40096,6 +41437,12 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPosit
|
|
|
40096
41437
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
|
|
40097
41438
|
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
|
|
40098
41439
|
const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
|
|
41440
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "LiveUtils.getPositionHighestProfitMinutes";
|
|
41441
|
+
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "LiveUtils.getPositionMaxDrawdownMinutes";
|
|
41442
|
+
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "LiveUtils.getPositionMaxDrawdownPrice";
|
|
41443
|
+
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "LiveUtils.getPositionMaxDrawdownTimestamp";
|
|
41444
|
+
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionMaxDrawdownPnlPercentage";
|
|
41445
|
+
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionMaxDrawdownPnlCost";
|
|
40099
41446
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
40100
41447
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
40101
41448
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -41299,6 +42646,199 @@ class LiveUtils {
|
|
|
41299
42646
|
frameName: "",
|
|
41300
42647
|
});
|
|
41301
42648
|
};
|
|
42649
|
+
/**
|
|
42650
|
+
* Returns the number of minutes elapsed since the highest profit price was recorded.
|
|
42651
|
+
*
|
|
42652
|
+
* Alias for getPositionDrawdownMinutes — measures how long the position has been
|
|
42653
|
+
* pulling back from its peak profit level.
|
|
42654
|
+
* Zero when called at the exact moment the peak was set.
|
|
42655
|
+
*
|
|
42656
|
+
* Returns null if no pending signal exists.
|
|
42657
|
+
*
|
|
42658
|
+
* @param symbol - Trading pair symbol
|
|
42659
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42660
|
+
* @returns Minutes since last profit peak, or null if no active position
|
|
42661
|
+
*/
|
|
42662
|
+
this.getPositionHighestProfitMinutes = async (symbol, context) => {
|
|
42663
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, {
|
|
42664
|
+
symbol,
|
|
42665
|
+
context,
|
|
42666
|
+
});
|
|
42667
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
42668
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
42669
|
+
{
|
|
42670
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
42671
|
+
riskName &&
|
|
42672
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
|
|
42673
|
+
riskList &&
|
|
42674
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
42675
|
+
actions &&
|
|
42676
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
|
|
42677
|
+
}
|
|
42678
|
+
return await bt.strategyCoreService.getPositionHighestProfitMinutes(false, symbol, {
|
|
42679
|
+
strategyName: context.strategyName,
|
|
42680
|
+
exchangeName: context.exchangeName,
|
|
42681
|
+
frameName: "",
|
|
42682
|
+
});
|
|
42683
|
+
};
|
|
42684
|
+
/**
|
|
42685
|
+
* Returns the number of minutes elapsed since the worst loss price was recorded.
|
|
42686
|
+
*
|
|
42687
|
+
* Measures how long ago the deepest drawdown point occurred.
|
|
42688
|
+
* Zero when called at the exact moment the trough was set.
|
|
42689
|
+
*
|
|
42690
|
+
* Returns null if no pending signal exists.
|
|
42691
|
+
*
|
|
42692
|
+
* @param symbol - Trading pair symbol
|
|
42693
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42694
|
+
* @returns Minutes since last drawdown trough, or null if no active position
|
|
42695
|
+
*/
|
|
42696
|
+
this.getPositionMaxDrawdownMinutes = async (symbol, context) => {
|
|
42697
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, {
|
|
42698
|
+
symbol,
|
|
42699
|
+
context,
|
|
42700
|
+
});
|
|
42701
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
42702
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
42703
|
+
{
|
|
42704
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
42705
|
+
riskName &&
|
|
42706
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
|
|
42707
|
+
riskList &&
|
|
42708
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
42709
|
+
actions &&
|
|
42710
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
|
|
42711
|
+
}
|
|
42712
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownMinutes(false, symbol, {
|
|
42713
|
+
strategyName: context.strategyName,
|
|
42714
|
+
exchangeName: context.exchangeName,
|
|
42715
|
+
frameName: "",
|
|
42716
|
+
});
|
|
42717
|
+
};
|
|
42718
|
+
/**
|
|
42719
|
+
* Returns the worst price reached in the loss direction during this position's life.
|
|
42720
|
+
*
|
|
42721
|
+
* Returns null if no pending signal exists.
|
|
42722
|
+
*
|
|
42723
|
+
* @param symbol - Trading pair symbol
|
|
42724
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42725
|
+
* @returns price or null if no active position
|
|
42726
|
+
*/
|
|
42727
|
+
this.getPositionMaxDrawdownPrice = async (symbol, context) => {
|
|
42728
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, {
|
|
42729
|
+
symbol,
|
|
42730
|
+
context,
|
|
42731
|
+
});
|
|
42732
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
42733
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
42734
|
+
{
|
|
42735
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
42736
|
+
riskName &&
|
|
42737
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
|
|
42738
|
+
riskList &&
|
|
42739
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
42740
|
+
actions &&
|
|
42741
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
|
|
42742
|
+
}
|
|
42743
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPrice(false, symbol, {
|
|
42744
|
+
strategyName: context.strategyName,
|
|
42745
|
+
exchangeName: context.exchangeName,
|
|
42746
|
+
frameName: "",
|
|
42747
|
+
});
|
|
42748
|
+
};
|
|
42749
|
+
/**
|
|
42750
|
+
* Returns the timestamp when the worst loss price was recorded during this position's life.
|
|
42751
|
+
*
|
|
42752
|
+
* Returns null if no pending signal exists.
|
|
42753
|
+
*
|
|
42754
|
+
* @param symbol - Trading pair symbol
|
|
42755
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42756
|
+
* @returns timestamp in milliseconds or null if no active position
|
|
42757
|
+
*/
|
|
42758
|
+
this.getPositionMaxDrawdownTimestamp = async (symbol, context) => {
|
|
42759
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, {
|
|
42760
|
+
symbol,
|
|
42761
|
+
context,
|
|
42762
|
+
});
|
|
42763
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
42764
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
42765
|
+
{
|
|
42766
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
42767
|
+
riskName &&
|
|
42768
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
|
|
42769
|
+
riskList &&
|
|
42770
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
42771
|
+
actions &&
|
|
42772
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
|
|
42773
|
+
}
|
|
42774
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownTimestamp(false, symbol, {
|
|
42775
|
+
strategyName: context.strategyName,
|
|
42776
|
+
exchangeName: context.exchangeName,
|
|
42777
|
+
frameName: "",
|
|
42778
|
+
});
|
|
42779
|
+
};
|
|
42780
|
+
/**
|
|
42781
|
+
* Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
|
|
42782
|
+
*
|
|
42783
|
+
* Returns null if no pending signal exists.
|
|
42784
|
+
*
|
|
42785
|
+
* @param symbol - Trading pair symbol
|
|
42786
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42787
|
+
* @returns PnL percentage or null if no active position
|
|
42788
|
+
*/
|
|
42789
|
+
this.getPositionMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
42790
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
42791
|
+
symbol,
|
|
42792
|
+
context,
|
|
42793
|
+
});
|
|
42794
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42795
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42796
|
+
{
|
|
42797
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
42798
|
+
riskName &&
|
|
42799
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42800
|
+
riskList &&
|
|
42801
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42802
|
+
actions &&
|
|
42803
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42804
|
+
}
|
|
42805
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPnlPercentage(false, symbol, {
|
|
42806
|
+
strategyName: context.strategyName,
|
|
42807
|
+
exchangeName: context.exchangeName,
|
|
42808
|
+
frameName: "",
|
|
42809
|
+
});
|
|
42810
|
+
};
|
|
42811
|
+
/**
|
|
42812
|
+
* Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
|
|
42813
|
+
*
|
|
42814
|
+
* Returns null if no pending signal exists.
|
|
42815
|
+
*
|
|
42816
|
+
* @param symbol - Trading pair symbol
|
|
42817
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42818
|
+
* @returns PnL cost or null if no active position
|
|
42819
|
+
*/
|
|
42820
|
+
this.getPositionMaxDrawdownPnlCost = async (symbol, context) => {
|
|
42821
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, {
|
|
42822
|
+
symbol,
|
|
42823
|
+
context,
|
|
42824
|
+
});
|
|
42825
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
42826
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
42827
|
+
{
|
|
42828
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
42829
|
+
riskName &&
|
|
42830
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
|
|
42831
|
+
riskList &&
|
|
42832
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
42833
|
+
actions &&
|
|
42834
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
|
|
42835
|
+
}
|
|
42836
|
+
return await bt.strategyCoreService.getPositionMaxDrawdownPnlCost(false, symbol, {
|
|
42837
|
+
strategyName: context.strategyName,
|
|
42838
|
+
exchangeName: context.exchangeName,
|
|
42839
|
+
frameName: "",
|
|
42840
|
+
});
|
|
42841
|
+
};
|
|
41302
42842
|
/**
|
|
41303
42843
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
41304
42844
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -47736,6 +49276,98 @@ class HighestProfitUtils {
|
|
|
47736
49276
|
*/
|
|
47737
49277
|
const HighestProfit = new HighestProfitUtils();
|
|
47738
49278
|
|
|
49279
|
+
const MAX_DRAWDOWN_METHOD_NAME_GET_DATA = "MaxDrawdownUtils.getData";
|
|
49280
|
+
const MAX_DRAWDOWN_METHOD_NAME_GET_REPORT = "MaxDrawdownUtils.getReport";
|
|
49281
|
+
const MAX_DRAWDOWN_METHOD_NAME_DUMP = "MaxDrawdownUtils.dump";
|
|
49282
|
+
/**
|
|
49283
|
+
* Utility class for accessing max drawdown reports and statistics.
|
|
49284
|
+
*
|
|
49285
|
+
* Provides static-like methods (via singleton instance) to retrieve data
|
|
49286
|
+
* accumulated by MaxDrawdownMarkdownService from maxDrawdownSubject events.
|
|
49287
|
+
*
|
|
49288
|
+
* @example
|
|
49289
|
+
* ```typescript
|
|
49290
|
+
* import { MaxDrawdown } from "backtest-kit";
|
|
49291
|
+
*
|
|
49292
|
+
* const stats = await MaxDrawdown.getData("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
49293
|
+
* const report = await MaxDrawdown.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
49294
|
+
* await MaxDrawdown.dump("BTCUSDT", { strategyName, exchangeName, frameName });
|
|
49295
|
+
* ```
|
|
49296
|
+
*/
|
|
49297
|
+
class MaxDrawdownUtils {
|
|
49298
|
+
constructor() {
|
|
49299
|
+
/**
|
|
49300
|
+
* Retrieves statistical data from accumulated max drawdown events.
|
|
49301
|
+
*
|
|
49302
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
49303
|
+
* @param context - Execution context
|
|
49304
|
+
* @param backtest - Whether to query backtest data
|
|
49305
|
+
* @returns Promise resolving to MaxDrawdownStatisticsModel
|
|
49306
|
+
*/
|
|
49307
|
+
this.getData = async (symbol, context, backtest = false) => {
|
|
49308
|
+
bt.loggerService.info(MAX_DRAWDOWN_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
49309
|
+
bt.strategyValidationService.validate(context.strategyName, MAX_DRAWDOWN_METHOD_NAME_GET_DATA);
|
|
49310
|
+
bt.exchangeValidationService.validate(context.exchangeName, MAX_DRAWDOWN_METHOD_NAME_GET_DATA);
|
|
49311
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, MAX_DRAWDOWN_METHOD_NAME_GET_DATA);
|
|
49312
|
+
{
|
|
49313
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
49314
|
+
riskName && bt.riskValidationService.validate(riskName, MAX_DRAWDOWN_METHOD_NAME_GET_DATA);
|
|
49315
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, MAX_DRAWDOWN_METHOD_NAME_GET_DATA));
|
|
49316
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, MAX_DRAWDOWN_METHOD_NAME_GET_DATA));
|
|
49317
|
+
}
|
|
49318
|
+
return await bt.maxDrawdownMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
49319
|
+
};
|
|
49320
|
+
/**
|
|
49321
|
+
* Generates a markdown report with all max drawdown events for a symbol-strategy pair.
|
|
49322
|
+
*
|
|
49323
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
49324
|
+
* @param context - Execution context
|
|
49325
|
+
* @param backtest - Whether to query backtest data
|
|
49326
|
+
* @param columns - Optional column configuration
|
|
49327
|
+
* @returns Promise resolving to markdown formatted report string
|
|
49328
|
+
*/
|
|
49329
|
+
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
49330
|
+
bt.loggerService.info(MAX_DRAWDOWN_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
49331
|
+
bt.strategyValidationService.validate(context.strategyName, MAX_DRAWDOWN_METHOD_NAME_GET_REPORT);
|
|
49332
|
+
bt.exchangeValidationService.validate(context.exchangeName, MAX_DRAWDOWN_METHOD_NAME_GET_REPORT);
|
|
49333
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, MAX_DRAWDOWN_METHOD_NAME_GET_REPORT);
|
|
49334
|
+
{
|
|
49335
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
49336
|
+
riskName && bt.riskValidationService.validate(riskName, MAX_DRAWDOWN_METHOD_NAME_GET_REPORT);
|
|
49337
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, MAX_DRAWDOWN_METHOD_NAME_GET_REPORT));
|
|
49338
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, MAX_DRAWDOWN_METHOD_NAME_GET_REPORT));
|
|
49339
|
+
}
|
|
49340
|
+
return await bt.maxDrawdownMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
|
|
49341
|
+
};
|
|
49342
|
+
/**
|
|
49343
|
+
* Generates and saves a markdown report to file.
|
|
49344
|
+
*
|
|
49345
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
49346
|
+
* @param context - Execution context
|
|
49347
|
+
* @param backtest - Whether to query backtest data
|
|
49348
|
+
* @param path - Output directory path (default: "./dump/max_drawdown")
|
|
49349
|
+
* @param columns - Optional column configuration
|
|
49350
|
+
*/
|
|
49351
|
+
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
49352
|
+
bt.loggerService.info(MAX_DRAWDOWN_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
49353
|
+
bt.strategyValidationService.validate(context.strategyName, MAX_DRAWDOWN_METHOD_NAME_DUMP);
|
|
49354
|
+
bt.exchangeValidationService.validate(context.exchangeName, MAX_DRAWDOWN_METHOD_NAME_DUMP);
|
|
49355
|
+
context.frameName && bt.frameValidationService.validate(context.frameName, MAX_DRAWDOWN_METHOD_NAME_DUMP);
|
|
49356
|
+
{
|
|
49357
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
49358
|
+
riskName && bt.riskValidationService.validate(riskName, MAX_DRAWDOWN_METHOD_NAME_DUMP);
|
|
49359
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, MAX_DRAWDOWN_METHOD_NAME_DUMP));
|
|
49360
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, MAX_DRAWDOWN_METHOD_NAME_DUMP));
|
|
49361
|
+
}
|
|
49362
|
+
await bt.maxDrawdownMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
|
|
49363
|
+
};
|
|
49364
|
+
}
|
|
49365
|
+
}
|
|
49366
|
+
/**
|
|
49367
|
+
* Global singleton instance of MaxDrawdownUtils.
|
|
49368
|
+
*/
|
|
49369
|
+
const MaxDrawdown = new MaxDrawdownUtils();
|
|
49370
|
+
|
|
47739
49371
|
/**
|
|
47740
49372
|
* Utility class containing predefined trading constants for take-profit and stop-loss levels.
|
|
47741
49373
|
*
|
|
@@ -52600,4 +54232,4 @@ const validateSignal = (signal, currentPrice) => {
|
|
|
52600
54232
|
return !errors.length;
|
|
52601
54233
|
};
|
|
52602
54234
|
|
|
52603
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|
|
54235
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
|