backtest-kit 6.9.0 → 6.10.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 +639 -24
- package/build/index.mjs +636 -25
- package/package.json +3 -3
- package/types.d.ts +378 -21
package/build/index.cjs
CHANGED
|
@@ -7990,6 +7990,90 @@ class ClientStrategy {
|
|
|
7990
7990
|
}
|
|
7991
7991
|
return this._pendingSignal._fall.pnlCost;
|
|
7992
7992
|
}
|
|
7993
|
+
/**
|
|
7994
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
7995
|
+
*
|
|
7996
|
+
* Measures how much PnL% the position has given back from its best point.
|
|
7997
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
7998
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
7999
|
+
*
|
|
8000
|
+
* Returns null if no pending signal exists.
|
|
8001
|
+
*
|
|
8002
|
+
* @param symbol - Trading pair symbol
|
|
8003
|
+
* @param currentPrice - Current market price
|
|
8004
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
8005
|
+
*/
|
|
8006
|
+
async getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice) {
|
|
8007
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlPercentage", { symbol, currentPrice });
|
|
8008
|
+
if (!this._pendingSignal) {
|
|
8009
|
+
return null;
|
|
8010
|
+
}
|
|
8011
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8012
|
+
return Math.max(0, this._pendingSignal._peak.pnlPercentage - currentPnl.pnlPercentage);
|
|
8013
|
+
}
|
|
8014
|
+
/**
|
|
8015
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
8016
|
+
*
|
|
8017
|
+
* Measures how much PnL cost the position has given back from its best point.
|
|
8018
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
8019
|
+
* Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
|
|
8020
|
+
*
|
|
8021
|
+
* Returns null if no pending signal exists.
|
|
8022
|
+
*
|
|
8023
|
+
* @param symbol - Trading pair symbol
|
|
8024
|
+
* @param currentPrice - Current market price
|
|
8025
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
8026
|
+
*/
|
|
8027
|
+
async getPositionHighestProfitDistancePnlCost(symbol, currentPrice) {
|
|
8028
|
+
this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlCost", { symbol, currentPrice });
|
|
8029
|
+
if (!this._pendingSignal) {
|
|
8030
|
+
return null;
|
|
8031
|
+
}
|
|
8032
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8033
|
+
return Math.max(0, this._pendingSignal._peak.pnlCost - currentPnl.pnlCost);
|
|
8034
|
+
}
|
|
8035
|
+
/**
|
|
8036
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
8037
|
+
*
|
|
8038
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
8039
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
8040
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
8041
|
+
*
|
|
8042
|
+
* Returns null if no pending signal exists.
|
|
8043
|
+
*
|
|
8044
|
+
* @param symbol - Trading pair symbol
|
|
8045
|
+
* @param currentPrice - Current market price
|
|
8046
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
8047
|
+
*/
|
|
8048
|
+
async getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice) {
|
|
8049
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlPercentage", { symbol, currentPrice });
|
|
8050
|
+
if (!this._pendingSignal) {
|
|
8051
|
+
return null;
|
|
8052
|
+
}
|
|
8053
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8054
|
+
return Math.max(0, currentPnl.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
|
|
8055
|
+
}
|
|
8056
|
+
/**
|
|
8057
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
8058
|
+
*
|
|
8059
|
+
* Measures how much the position has recovered from its deepest loss point.
|
|
8060
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
8061
|
+
* Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
|
|
8062
|
+
*
|
|
8063
|
+
* Returns null if no pending signal exists.
|
|
8064
|
+
*
|
|
8065
|
+
* @param symbol - Trading pair symbol
|
|
8066
|
+
* @param currentPrice - Current market price
|
|
8067
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
8068
|
+
*/
|
|
8069
|
+
async getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice) {
|
|
8070
|
+
this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlCost", { symbol, currentPrice });
|
|
8071
|
+
if (!this._pendingSignal) {
|
|
8072
|
+
return null;
|
|
8073
|
+
}
|
|
8074
|
+
const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
8075
|
+
return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
|
|
8076
|
+
}
|
|
7993
8077
|
/**
|
|
7994
8078
|
* Performs a single tick of strategy execution.
|
|
7995
8079
|
*
|
|
@@ -11097,6 +11181,90 @@ class StrategyConnectionService {
|
|
|
11097
11181
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11098
11182
|
return await strategy.getPositionMaxDrawdownPnlCost(symbol);
|
|
11099
11183
|
};
|
|
11184
|
+
/**
|
|
11185
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
11186
|
+
*
|
|
11187
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11188
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlPercentage().
|
|
11189
|
+
* Returns null if no pending signal exists.
|
|
11190
|
+
*
|
|
11191
|
+
* @param backtest - Whether running in backtest mode
|
|
11192
|
+
* @param symbol - Trading pair symbol
|
|
11193
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11194
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
11195
|
+
*/
|
|
11196
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
11197
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlPercentage", {
|
|
11198
|
+
symbol,
|
|
11199
|
+
context,
|
|
11200
|
+
});
|
|
11201
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11202
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11203
|
+
return await strategy.getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice);
|
|
11204
|
+
};
|
|
11205
|
+
/**
|
|
11206
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
11207
|
+
*
|
|
11208
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11209
|
+
* ClientStrategy.getPositionHighestProfitDistancePnlCost().
|
|
11210
|
+
* Returns null if no pending signal exists.
|
|
11211
|
+
*
|
|
11212
|
+
* @param backtest - Whether running in backtest mode
|
|
11213
|
+
* @param symbol - Trading pair symbol
|
|
11214
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11215
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
11216
|
+
*/
|
|
11217
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
11218
|
+
this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlCost", {
|
|
11219
|
+
symbol,
|
|
11220
|
+
context,
|
|
11221
|
+
});
|
|
11222
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11223
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11224
|
+
return await strategy.getPositionHighestProfitDistancePnlCost(symbol, currentPrice);
|
|
11225
|
+
};
|
|
11226
|
+
/**
|
|
11227
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
11228
|
+
*
|
|
11229
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11230
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlPercentage().
|
|
11231
|
+
* Returns null if no pending signal exists.
|
|
11232
|
+
*
|
|
11233
|
+
* @param backtest - Whether running in backtest mode
|
|
11234
|
+
* @param symbol - Trading pair symbol
|
|
11235
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11236
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
11237
|
+
*/
|
|
11238
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
11239
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
11240
|
+
symbol,
|
|
11241
|
+
context,
|
|
11242
|
+
});
|
|
11243
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11244
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11245
|
+
return await strategy.getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice);
|
|
11246
|
+
};
|
|
11247
|
+
/**
|
|
11248
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
11249
|
+
*
|
|
11250
|
+
* Resolves current price via priceMetaService and delegates to
|
|
11251
|
+
* ClientStrategy.getPositionHighestMaxDrawdownPnlCost().
|
|
11252
|
+
* Returns null if no pending signal exists.
|
|
11253
|
+
*
|
|
11254
|
+
* @param backtest - Whether running in backtest mode
|
|
11255
|
+
* @param symbol - Trading pair symbol
|
|
11256
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
11257
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
11258
|
+
*/
|
|
11259
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
11260
|
+
this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlCost", {
|
|
11261
|
+
symbol,
|
|
11262
|
+
context,
|
|
11263
|
+
});
|
|
11264
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
11265
|
+
const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
|
|
11266
|
+
return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
|
|
11267
|
+
};
|
|
11100
11268
|
/**
|
|
11101
11269
|
* Disposes the ClientStrategy instance for the given context.
|
|
11102
11270
|
*
|
|
@@ -15258,6 +15426,82 @@ class StrategyCoreService {
|
|
|
15258
15426
|
await this.validate(context);
|
|
15259
15427
|
return await this.strategyConnectionService.getPositionMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15260
15428
|
};
|
|
15429
|
+
/**
|
|
15430
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
15431
|
+
*
|
|
15432
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlPercentage().
|
|
15433
|
+
* Returns null if no pending signal exists.
|
|
15434
|
+
*
|
|
15435
|
+
* @param backtest - Whether running in backtest mode
|
|
15436
|
+
* @param symbol - Trading pair symbol
|
|
15437
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15438
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
15439
|
+
*/
|
|
15440
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
|
|
15441
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlPercentage", {
|
|
15442
|
+
symbol,
|
|
15443
|
+
context,
|
|
15444
|
+
});
|
|
15445
|
+
await this.validate(context);
|
|
15446
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlPercentage(backtest, symbol, context);
|
|
15447
|
+
};
|
|
15448
|
+
/**
|
|
15449
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
15450
|
+
*
|
|
15451
|
+
* Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlCost().
|
|
15452
|
+
* Returns null if no pending signal exists.
|
|
15453
|
+
*
|
|
15454
|
+
* @param backtest - Whether running in backtest mode
|
|
15455
|
+
* @param symbol - Trading pair symbol
|
|
15456
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15457
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
15458
|
+
*/
|
|
15459
|
+
this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
|
|
15460
|
+
this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlCost", {
|
|
15461
|
+
symbol,
|
|
15462
|
+
context,
|
|
15463
|
+
});
|
|
15464
|
+
await this.validate(context);
|
|
15465
|
+
return await this.strategyConnectionService.getPositionHighestProfitDistancePnlCost(backtest, symbol, context);
|
|
15466
|
+
};
|
|
15467
|
+
/**
|
|
15468
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
15469
|
+
*
|
|
15470
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage().
|
|
15471
|
+
* Returns null if no pending signal exists.
|
|
15472
|
+
*
|
|
15473
|
+
* @param backtest - Whether running in backtest mode
|
|
15474
|
+
* @param symbol - Trading pair symbol
|
|
15475
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15476
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
15477
|
+
*/
|
|
15478
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
|
|
15479
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlPercentage", {
|
|
15480
|
+
symbol,
|
|
15481
|
+
context,
|
|
15482
|
+
});
|
|
15483
|
+
await this.validate(context);
|
|
15484
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage(backtest, symbol, context);
|
|
15485
|
+
};
|
|
15486
|
+
/**
|
|
15487
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
15488
|
+
*
|
|
15489
|
+
* Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlCost().
|
|
15490
|
+
* Returns null if no pending signal exists.
|
|
15491
|
+
*
|
|
15492
|
+
* @param backtest - Whether running in backtest mode
|
|
15493
|
+
* @param symbol - Trading pair symbol
|
|
15494
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
15495
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
15496
|
+
*/
|
|
15497
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
|
|
15498
|
+
this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlCost", {
|
|
15499
|
+
symbol,
|
|
15500
|
+
context,
|
|
15501
|
+
});
|
|
15502
|
+
await this.validate(context);
|
|
15503
|
+
return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
|
|
15504
|
+
};
|
|
15261
15505
|
}
|
|
15262
15506
|
}
|
|
15263
15507
|
|
|
@@ -34913,6 +35157,10 @@ const GET_POSITION_MAX_DRAWDOWN_PRICE_METHOD_NAME = "strategy.getPositionMaxDraw
|
|
|
34913
35157
|
const GET_POSITION_MAX_DRAWDOWN_TIMESTAMP_METHOD_NAME = "strategy.getPositionMaxDrawdownTimestamp";
|
|
34914
35158
|
const GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlPercentage";
|
|
34915
35159
|
const GET_POSITION_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlCost";
|
|
35160
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlPercentage";
|
|
35161
|
+
const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
|
|
35162
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
|
|
35163
|
+
const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
|
|
34916
35164
|
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
34917
35165
|
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
34918
35166
|
const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
|
|
@@ -36547,6 +36795,122 @@ async function getPositionMaxDrawdownPnlCost(symbol) {
|
|
|
36547
36795
|
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36548
36796
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36549
36797
|
}
|
|
36798
|
+
/**
|
|
36799
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
36800
|
+
*
|
|
36801
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
36802
|
+
* Returns null if no pending signal exists.
|
|
36803
|
+
*
|
|
36804
|
+
* @param symbol - Trading pair symbol
|
|
36805
|
+
* @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
|
|
36806
|
+
*
|
|
36807
|
+
* @example
|
|
36808
|
+
* ```typescript
|
|
36809
|
+
* import { getPositionHighestProfitDistancePnlPercentage } from "backtest-kit";
|
|
36810
|
+
*
|
|
36811
|
+
* const dist = await getPositionHighestProfitDistancePnlPercentage("BTCUSDT");
|
|
36812
|
+
* // e.g. 1.5 (gave back 1.5% from peak)
|
|
36813
|
+
* ```
|
|
36814
|
+
*/
|
|
36815
|
+
async function getPositionHighestProfitDistancePnlPercentage(symbol) {
|
|
36816
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36817
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36818
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires an execution context");
|
|
36819
|
+
}
|
|
36820
|
+
if (!MethodContextService.hasContext()) {
|
|
36821
|
+
throw new Error("getPositionHighestProfitDistancePnlPercentage requires a method context");
|
|
36822
|
+
}
|
|
36823
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36824
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36825
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36826
|
+
}
|
|
36827
|
+
/**
|
|
36828
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
36829
|
+
*
|
|
36830
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
36831
|
+
* Returns null if no pending signal exists.
|
|
36832
|
+
*
|
|
36833
|
+
* @param symbol - Trading pair symbol
|
|
36834
|
+
* @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
|
|
36835
|
+
*
|
|
36836
|
+
* @example
|
|
36837
|
+
* ```typescript
|
|
36838
|
+
* import { getPositionHighestProfitDistancePnlCost } from "backtest-kit";
|
|
36839
|
+
*
|
|
36840
|
+
* const dist = await getPositionHighestProfitDistancePnlCost("BTCUSDT");
|
|
36841
|
+
* // e.g. 3.2 (gave back $3.2 from peak)
|
|
36842
|
+
* ```
|
|
36843
|
+
*/
|
|
36844
|
+
async function getPositionHighestProfitDistancePnlCost(symbol) {
|
|
36845
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
|
|
36846
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36847
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires an execution context");
|
|
36848
|
+
}
|
|
36849
|
+
if (!MethodContextService.hasContext()) {
|
|
36850
|
+
throw new Error("getPositionHighestProfitDistancePnlCost requires a method context");
|
|
36851
|
+
}
|
|
36852
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36853
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36854
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36855
|
+
}
|
|
36856
|
+
/**
|
|
36857
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
36858
|
+
*
|
|
36859
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
36860
|
+
* Returns null if no pending signal exists.
|
|
36861
|
+
*
|
|
36862
|
+
* @param symbol - Trading pair symbol
|
|
36863
|
+
* @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
|
|
36864
|
+
*
|
|
36865
|
+
* @example
|
|
36866
|
+
* ```typescript
|
|
36867
|
+
* import { getPositionHighestMaxDrawdownPnlPercentage } from "backtest-kit";
|
|
36868
|
+
*
|
|
36869
|
+
* const dist = await getPositionHighestMaxDrawdownPnlPercentage("BTCUSDT");
|
|
36870
|
+
* // e.g. 2.1 (recovered 2.1% from trough)
|
|
36871
|
+
* ```
|
|
36872
|
+
*/
|
|
36873
|
+
async function getPositionHighestMaxDrawdownPnlPercentage(symbol) {
|
|
36874
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME, { symbol });
|
|
36875
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36876
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires an execution context");
|
|
36877
|
+
}
|
|
36878
|
+
if (!MethodContextService.hasContext()) {
|
|
36879
|
+
throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires a method context");
|
|
36880
|
+
}
|
|
36881
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36882
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36883
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36884
|
+
}
|
|
36885
|
+
/**
|
|
36886
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
36887
|
+
*
|
|
36888
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
36889
|
+
* Returns null if no pending signal exists.
|
|
36890
|
+
*
|
|
36891
|
+
* @param symbol - Trading pair symbol
|
|
36892
|
+
* @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
|
|
36893
|
+
*
|
|
36894
|
+
* @example
|
|
36895
|
+
* ```typescript
|
|
36896
|
+
* import { getPositionHighestMaxDrawdownPnlCost } from "backtest-kit";
|
|
36897
|
+
*
|
|
36898
|
+
* const dist = await getPositionHighestMaxDrawdownPnlCost("BTCUSDT");
|
|
36899
|
+
* // e.g. 4.8 (recovered $4.8 from trough)
|
|
36900
|
+
* ```
|
|
36901
|
+
*/
|
|
36902
|
+
async function getPositionHighestMaxDrawdownPnlCost(symbol) {
|
|
36903
|
+
backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME, { symbol });
|
|
36904
|
+
if (!ExecutionContextService.hasContext()) {
|
|
36905
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires an execution context");
|
|
36906
|
+
}
|
|
36907
|
+
if (!MethodContextService.hasContext()) {
|
|
36908
|
+
throw new Error("getPositionHighestMaxDrawdownPnlCost requires a method context");
|
|
36909
|
+
}
|
|
36910
|
+
const { backtest: isBacktest } = backtest.executionContextService.context;
|
|
36911
|
+
const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
|
|
36912
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
36913
|
+
}
|
|
36550
36914
|
/**
|
|
36551
36915
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
36552
36916
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -38216,6 +38580,10 @@ const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "BacktestUtils.getP
|
|
|
38216
38580
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "BacktestUtils.getPositionMaxDrawdownTimestamp";
|
|
38217
38581
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionMaxDrawdownPnlPercentage";
|
|
38218
38582
|
const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionMaxDrawdownPnlCost";
|
|
38583
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
38584
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
|
|
38585
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
38586
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
38219
38587
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
38220
38588
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
38221
38589
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
@@ -39485,6 +39853,118 @@ class BacktestUtils {
|
|
|
39485
39853
|
}
|
|
39486
39854
|
return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(true, symbol, context);
|
|
39487
39855
|
};
|
|
39856
|
+
/**
|
|
39857
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
39858
|
+
*
|
|
39859
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
39860
|
+
* Returns null if no pending signal exists.
|
|
39861
|
+
*
|
|
39862
|
+
* @param symbol - Trading pair symbol
|
|
39863
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39864
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
39865
|
+
*/
|
|
39866
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
39867
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
39868
|
+
symbol,
|
|
39869
|
+
context,
|
|
39870
|
+
});
|
|
39871
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39872
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39873
|
+
{
|
|
39874
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39875
|
+
riskName &&
|
|
39876
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
39877
|
+
riskList &&
|
|
39878
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39879
|
+
actions &&
|
|
39880
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
39881
|
+
}
|
|
39882
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(true, symbol, context);
|
|
39883
|
+
};
|
|
39884
|
+
/**
|
|
39885
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
39886
|
+
*
|
|
39887
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
39888
|
+
* Returns null if no pending signal exists.
|
|
39889
|
+
*
|
|
39890
|
+
* @param symbol - Trading pair symbol
|
|
39891
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39892
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
39893
|
+
*/
|
|
39894
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
39895
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
39896
|
+
symbol,
|
|
39897
|
+
context,
|
|
39898
|
+
});
|
|
39899
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39900
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39901
|
+
{
|
|
39902
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39903
|
+
riskName &&
|
|
39904
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
39905
|
+
riskList &&
|
|
39906
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39907
|
+
actions &&
|
|
39908
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
39909
|
+
}
|
|
39910
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(true, symbol, context);
|
|
39911
|
+
};
|
|
39912
|
+
/**
|
|
39913
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
39914
|
+
*
|
|
39915
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
39916
|
+
* Returns null if no pending signal exists.
|
|
39917
|
+
*
|
|
39918
|
+
* @param symbol - Trading pair symbol
|
|
39919
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39920
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
39921
|
+
*/
|
|
39922
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
39923
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
39924
|
+
symbol,
|
|
39925
|
+
context,
|
|
39926
|
+
});
|
|
39927
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39928
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39929
|
+
{
|
|
39930
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39931
|
+
riskName &&
|
|
39932
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
39933
|
+
riskList &&
|
|
39934
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39935
|
+
actions &&
|
|
39936
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
39937
|
+
}
|
|
39938
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(true, symbol, context);
|
|
39939
|
+
};
|
|
39940
|
+
/**
|
|
39941
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
39942
|
+
*
|
|
39943
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
39944
|
+
* Returns null if no pending signal exists.
|
|
39945
|
+
*
|
|
39946
|
+
* @param symbol - Trading pair symbol
|
|
39947
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
39948
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
39949
|
+
*/
|
|
39950
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
39951
|
+
backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
39952
|
+
symbol,
|
|
39953
|
+
context,
|
|
39954
|
+
});
|
|
39955
|
+
backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39956
|
+
backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39957
|
+
{
|
|
39958
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
39959
|
+
riskName &&
|
|
39960
|
+
backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
39961
|
+
riskList &&
|
|
39962
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39963
|
+
actions &&
|
|
39964
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
39965
|
+
}
|
|
39966
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
|
|
39967
|
+
};
|
|
39488
39968
|
/**
|
|
39489
39969
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
39490
39970
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -40610,6 +41090,10 @@ const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "LiveUtils.getPositionM
|
|
|
40610
41090
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "LiveUtils.getPositionMaxDrawdownTimestamp";
|
|
40611
41091
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionMaxDrawdownPnlPercentage";
|
|
40612
41092
|
const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionMaxDrawdownPnlCost";
|
|
41093
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getPositionHighestProfitDistancePnlPercentage";
|
|
41094
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
|
|
41095
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
|
|
41096
|
+
const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
|
|
40613
41097
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
40614
41098
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
40615
41099
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
@@ -42006,6 +42490,134 @@ class LiveUtils {
|
|
|
42006
42490
|
frameName: "",
|
|
42007
42491
|
});
|
|
42008
42492
|
};
|
|
42493
|
+
/**
|
|
42494
|
+
* Returns the distance in PnL percentage between the current price and the highest profit peak.
|
|
42495
|
+
*
|
|
42496
|
+
* Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
|
|
42497
|
+
* Returns null if no pending signal exists.
|
|
42498
|
+
*
|
|
42499
|
+
* @param symbol - Trading pair symbol
|
|
42500
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42501
|
+
* @returns drawdown distance in PnL% (≥ 0) or null if no active position
|
|
42502
|
+
*/
|
|
42503
|
+
this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
|
|
42504
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
|
|
42505
|
+
symbol,
|
|
42506
|
+
context,
|
|
42507
|
+
});
|
|
42508
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42509
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42510
|
+
{
|
|
42511
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42512
|
+
riskName &&
|
|
42513
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
|
|
42514
|
+
riskList &&
|
|
42515
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42516
|
+
actions &&
|
|
42517
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
|
|
42518
|
+
}
|
|
42519
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(false, symbol, {
|
|
42520
|
+
strategyName: context.strategyName,
|
|
42521
|
+
exchangeName: context.exchangeName,
|
|
42522
|
+
frameName: "",
|
|
42523
|
+
});
|
|
42524
|
+
};
|
|
42525
|
+
/**
|
|
42526
|
+
* Returns the distance in PnL cost between the current price and the highest profit peak.
|
|
42527
|
+
*
|
|
42528
|
+
* Computed as: max(0, peakPnlCost - currentPnlCost).
|
|
42529
|
+
* Returns null if no pending signal exists.
|
|
42530
|
+
*
|
|
42531
|
+
* @param symbol - Trading pair symbol
|
|
42532
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42533
|
+
* @returns drawdown distance in PnL cost (≥ 0) or null if no active position
|
|
42534
|
+
*/
|
|
42535
|
+
this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
|
|
42536
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
|
|
42537
|
+
symbol,
|
|
42538
|
+
context,
|
|
42539
|
+
});
|
|
42540
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42541
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42542
|
+
{
|
|
42543
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42544
|
+
riskName &&
|
|
42545
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
|
|
42546
|
+
riskList &&
|
|
42547
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42548
|
+
actions &&
|
|
42549
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
|
|
42550
|
+
}
|
|
42551
|
+
return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(false, symbol, {
|
|
42552
|
+
strategyName: context.strategyName,
|
|
42553
|
+
exchangeName: context.exchangeName,
|
|
42554
|
+
frameName: "",
|
|
42555
|
+
});
|
|
42556
|
+
};
|
|
42557
|
+
/**
|
|
42558
|
+
* Returns the distance in PnL percentage between the current price and the worst drawdown trough.
|
|
42559
|
+
*
|
|
42560
|
+
* Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
|
|
42561
|
+
* Returns null if no pending signal exists.
|
|
42562
|
+
*
|
|
42563
|
+
* @param symbol - Trading pair symbol
|
|
42564
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42565
|
+
* @returns recovery distance in PnL% (≥ 0) or null if no active position
|
|
42566
|
+
*/
|
|
42567
|
+
this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
|
|
42568
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
|
|
42569
|
+
symbol,
|
|
42570
|
+
context,
|
|
42571
|
+
});
|
|
42572
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42573
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42574
|
+
{
|
|
42575
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42576
|
+
riskName &&
|
|
42577
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
|
|
42578
|
+
riskList &&
|
|
42579
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42580
|
+
actions &&
|
|
42581
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
|
|
42582
|
+
}
|
|
42583
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(false, symbol, {
|
|
42584
|
+
strategyName: context.strategyName,
|
|
42585
|
+
exchangeName: context.exchangeName,
|
|
42586
|
+
frameName: "",
|
|
42587
|
+
});
|
|
42588
|
+
};
|
|
42589
|
+
/**
|
|
42590
|
+
* Returns the distance in PnL cost between the current price and the worst drawdown trough.
|
|
42591
|
+
*
|
|
42592
|
+
* Computed as: max(0, currentPnlCost - fallPnlCost).
|
|
42593
|
+
* Returns null if no pending signal exists.
|
|
42594
|
+
*
|
|
42595
|
+
* @param symbol - Trading pair symbol
|
|
42596
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
42597
|
+
* @returns recovery distance in PnL cost (≥ 0) or null if no active position
|
|
42598
|
+
*/
|
|
42599
|
+
this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
|
|
42600
|
+
backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
|
|
42601
|
+
symbol,
|
|
42602
|
+
context,
|
|
42603
|
+
});
|
|
42604
|
+
backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42605
|
+
backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42606
|
+
{
|
|
42607
|
+
const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
|
|
42608
|
+
riskName &&
|
|
42609
|
+
backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
|
|
42610
|
+
riskList &&
|
|
42611
|
+
riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42612
|
+
actions &&
|
|
42613
|
+
actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
|
|
42614
|
+
}
|
|
42615
|
+
return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(false, symbol, {
|
|
42616
|
+
strategyName: context.strategyName,
|
|
42617
|
+
exchangeName: context.exchangeName,
|
|
42618
|
+
frameName: "",
|
|
42619
|
+
});
|
|
42620
|
+
};
|
|
42009
42621
|
/**
|
|
42010
42622
|
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
42011
42623
|
* Use this to prevent duplicate DCA entries at the same price area.
|
|
@@ -53645,17 +54257,17 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
|
|
|
53645
54257
|
* @example
|
|
53646
54258
|
* ```typescript
|
|
53647
54259
|
* const instance = new IntervalFnInstance(mySignalFn, "1h");
|
|
53648
|
-
* await instance.run("BTCUSDT"); // →
|
|
53649
|
-
* await instance.run("BTCUSDT"); // → null
|
|
54260
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called)
|
|
54261
|
+
* await instance.run("BTCUSDT"); // → null (skipped, same interval)
|
|
53650
54262
|
* // After 1 hour passes:
|
|
53651
|
-
* await instance.run("BTCUSDT"); // →
|
|
54263
|
+
* await instance.run("BTCUSDT"); // → T | null (fn called again)
|
|
53652
54264
|
* ```
|
|
53653
54265
|
*/
|
|
53654
54266
|
class IntervalFnInstance {
|
|
53655
54267
|
/**
|
|
53656
54268
|
* Creates a new IntervalFnInstance.
|
|
53657
54269
|
*
|
|
53658
|
-
* @param fn -
|
|
54270
|
+
* @param fn - Function to fire once per interval
|
|
53659
54271
|
* @param interval - Candle interval that controls the firing boundary
|
|
53660
54272
|
*/
|
|
53661
54273
|
constructor(fn, interval) {
|
|
@@ -53675,7 +54287,7 @@ class IntervalFnInstance {
|
|
|
53675
54287
|
* Requires active method context and execution context.
|
|
53676
54288
|
*
|
|
53677
54289
|
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
53678
|
-
* @returns The
|
|
54290
|
+
* @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
|
|
53679
54291
|
* within the same interval or when `fn` itself returned `null`
|
|
53680
54292
|
* @throws Error if method context, execution context, or interval is missing
|
|
53681
54293
|
*/
|
|
@@ -53736,13 +54348,13 @@ class IntervalFnInstance {
|
|
|
53736
54348
|
*
|
|
53737
54349
|
* Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
|
|
53738
54350
|
*
|
|
53739
|
-
* @template T - Async function type: `(symbol: string, ...args) => Promise<
|
|
54351
|
+
* @template T - Async function type: `(symbol: string, ...args) => Promise<R | null>`
|
|
53740
54352
|
*
|
|
53741
54353
|
* @example
|
|
53742
54354
|
* ```typescript
|
|
53743
54355
|
* const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
|
|
53744
|
-
* await instance.run("BTCUSDT"); // →
|
|
53745
|
-
* await instance.run("BTCUSDT"); // → null
|
|
54356
|
+
* await instance.run("BTCUSDT"); // → R | null (fn called, result written to disk)
|
|
54357
|
+
* await instance.run("BTCUSDT"); // → null (record exists, already fired)
|
|
53746
54358
|
* ```
|
|
53747
54359
|
*/
|
|
53748
54360
|
class IntervalFileInstance {
|
|
@@ -53772,7 +54384,7 @@ class IntervalFileInstance {
|
|
|
53772
54384
|
this.interval = interval;
|
|
53773
54385
|
this.name = name;
|
|
53774
54386
|
/**
|
|
53775
|
-
* Execute the async
|
|
54387
|
+
* Execute the async function with persistent once-per-interval enforcement.
|
|
53776
54388
|
*
|
|
53777
54389
|
* Algorithm:
|
|
53778
54390
|
* 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
|
|
@@ -53784,13 +54396,13 @@ class IntervalFileInstance {
|
|
|
53784
54396
|
*
|
|
53785
54397
|
* Requires active method context and execution context.
|
|
53786
54398
|
*
|
|
53787
|
-
* @param
|
|
53788
|
-
* @returns The
|
|
54399
|
+
* @param symbol - Trading pair symbol (e.g. "BTCUSDT")
|
|
54400
|
+
* @returns The value on the first non-null fire, `null` if already fired this interval
|
|
53789
54401
|
* or if `fn` itself returned `null`
|
|
53790
54402
|
* @throws Error if method context, execution context, or interval is missing
|
|
53791
54403
|
*/
|
|
53792
|
-
this.run = async (
|
|
53793
|
-
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, {
|
|
54404
|
+
this.run = async (symbol) => {
|
|
54405
|
+
backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { symbol });
|
|
53794
54406
|
const step = INTERVAL_MINUTES[this.interval];
|
|
53795
54407
|
{
|
|
53796
54408
|
if (!MethodContextService.hasContext()) {
|
|
@@ -53803,7 +54415,6 @@ class IntervalFileInstance {
|
|
|
53803
54415
|
throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
|
|
53804
54416
|
}
|
|
53805
54417
|
}
|
|
53806
|
-
const [symbol] = args;
|
|
53807
54418
|
const { when } = backtest.executionContextService.context;
|
|
53808
54419
|
const alignedTs = align(when.getTime(), this.interval);
|
|
53809
54420
|
const bucket = `${this.name}_${this.interval}_${this.index}`;
|
|
@@ -53812,7 +54423,7 @@ class IntervalFileInstance {
|
|
|
53812
54423
|
if (cached !== null) {
|
|
53813
54424
|
return null;
|
|
53814
54425
|
}
|
|
53815
|
-
const result = await this.fn
|
|
54426
|
+
const result = await this.fn(symbol, when);
|
|
53816
54427
|
if (result !== null) {
|
|
53817
54428
|
await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
|
|
53818
54429
|
}
|
|
@@ -53843,8 +54454,8 @@ IntervalFileInstance._indexCounter = 0;
|
|
|
53843
54454
|
* import { Interval } from "./classes/Interval";
|
|
53844
54455
|
*
|
|
53845
54456
|
* const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
|
|
53846
|
-
* await fireOncePerHour("BTCUSDT"
|
|
53847
|
-
* await fireOncePerHour("BTCUSDT"
|
|
54457
|
+
* await fireOncePerHour("BTCUSDT"); // fn called — returns its result
|
|
54458
|
+
* await fireOncePerHour("BTCUSDT"); // returns null (same interval)
|
|
53848
54459
|
* ```
|
|
53849
54460
|
*/
|
|
53850
54461
|
class IntervalUtils {
|
|
@@ -53870,19 +54481,19 @@ class IntervalUtils {
|
|
|
53870
54481
|
*
|
|
53871
54482
|
* @param run - Signal function to wrap
|
|
53872
54483
|
* @param context.interval - Candle interval that controls the firing boundary
|
|
53873
|
-
* @returns Wrapped function with the same signature as `TIntervalFn
|
|
54484
|
+
* @returns Wrapped function with the same signature as `TIntervalFn<T>`, plus a `clear()` method
|
|
53874
54485
|
*
|
|
53875
54486
|
* @example
|
|
53876
54487
|
* ```typescript
|
|
53877
54488
|
* const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
|
|
53878
54489
|
*
|
|
53879
|
-
* await fireOnce("BTCUSDT"
|
|
53880
|
-
* await fireOnce("BTCUSDT"
|
|
54490
|
+
* await fireOnce("BTCUSDT"); // → T or null (fn called)
|
|
54491
|
+
* await fireOnce("BTCUSDT"); // → null (same interval, skipped)
|
|
53881
54492
|
* ```
|
|
53882
54493
|
*/
|
|
53883
54494
|
this.fn = (run, context) => {
|
|
53884
54495
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
|
|
53885
|
-
const wrappedFn = (symbol
|
|
54496
|
+
const wrappedFn = (symbol) => {
|
|
53886
54497
|
const instance = this._getInstance(run, context.interval);
|
|
53887
54498
|
return instance.run(symbol);
|
|
53888
54499
|
};
|
|
@@ -53919,7 +54530,7 @@ class IntervalUtils {
|
|
|
53919
54530
|
*
|
|
53920
54531
|
* @example
|
|
53921
54532
|
* ```typescript
|
|
53922
|
-
* const fetchSignal = async (symbol: string,
|
|
54533
|
+
* const fetchSignal = async (symbol: string, when: Date) => { ... };
|
|
53923
54534
|
* const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
|
|
53924
54535
|
* await fireOnce.clear(); // delete disk records so the function fires again next call
|
|
53925
54536
|
* ```
|
|
@@ -53929,9 +54540,9 @@ class IntervalUtils {
|
|
|
53929
54540
|
{
|
|
53930
54541
|
this._getFileInstance(run, context.interval, context.name);
|
|
53931
54542
|
}
|
|
53932
|
-
const wrappedFn = (
|
|
54543
|
+
const wrappedFn = (symbol) => {
|
|
53933
54544
|
const instance = this._getFileInstance(run, context.interval, context.name);
|
|
53934
|
-
return instance.run(
|
|
54545
|
+
return instance.run(symbol);
|
|
53935
54546
|
};
|
|
53936
54547
|
wrappedFn.clear = async () => {
|
|
53937
54548
|
backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
|
|
@@ -55212,9 +55823,13 @@ exports.getPositionEffectivePrice = getPositionEffectivePrice;
|
|
|
55212
55823
|
exports.getPositionEntries = getPositionEntries;
|
|
55213
55824
|
exports.getPositionEntryOverlap = getPositionEntryOverlap;
|
|
55214
55825
|
exports.getPositionEstimateMinutes = getPositionEstimateMinutes;
|
|
55826
|
+
exports.getPositionHighestMaxDrawdownPnlCost = getPositionHighestMaxDrawdownPnlCost;
|
|
55827
|
+
exports.getPositionHighestMaxDrawdownPnlPercentage = getPositionHighestMaxDrawdownPnlPercentage;
|
|
55215
55828
|
exports.getPositionHighestPnlCost = getPositionHighestPnlCost;
|
|
55216
55829
|
exports.getPositionHighestPnlPercentage = getPositionHighestPnlPercentage;
|
|
55217
55830
|
exports.getPositionHighestProfitBreakeven = getPositionHighestProfitBreakeven;
|
|
55831
|
+
exports.getPositionHighestProfitDistancePnlCost = getPositionHighestProfitDistancePnlCost;
|
|
55832
|
+
exports.getPositionHighestProfitDistancePnlPercentage = getPositionHighestProfitDistancePnlPercentage;
|
|
55218
55833
|
exports.getPositionHighestProfitMinutes = getPositionHighestProfitMinutes;
|
|
55219
55834
|
exports.getPositionHighestProfitPrice = getPositionHighestProfitPrice;
|
|
55220
55835
|
exports.getPositionHighestProfitTimestamp = getPositionHighestProfitTimestamp;
|