backtest-kit 3.7.1 → 3.8.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 +989 -6
- package/build/index.mjs +978 -7
- package/package.json +1 -1
- package/types.d.ts +451 -2
package/build/index.cjs
CHANGED
|
@@ -3318,8 +3318,8 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
3318
3318
|
? partial.effectivePrice * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
3319
3319
|
: partial.effectivePrice * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3320
3320
|
const priceCloseWithSlippage = signal.position === "long"
|
|
3321
|
-
? partial.
|
|
3322
|
-
: partial.
|
|
3321
|
+
? partial.currentPrice * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
3322
|
+
: partial.currentPrice * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3323
3323
|
const partialPnl = signal.position === "long"
|
|
3324
3324
|
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
3325
3325
|
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
@@ -3804,8 +3804,8 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3804
3804
|
const TO_PUBLIC_SIGNAL = (signal) => {
|
|
3805
3805
|
const hasTrailingSL = "_trailingPriceStopLoss" in signal && signal._trailingPriceStopLoss !== undefined;
|
|
3806
3806
|
const hasTrailingTP = "_trailingPriceTakeProfit" in signal && signal._trailingPriceTakeProfit !== undefined;
|
|
3807
|
-
const partialExecuted =
|
|
3808
|
-
? signal
|
|
3807
|
+
const partialExecuted = "_partial" in signal
|
|
3808
|
+
? getTotalClosed(signal).totalClosedPercent
|
|
3809
3809
|
: 0;
|
|
3810
3810
|
const totalEntries = ("_entry" in signal && Array.isArray(signal._entry))
|
|
3811
3811
|
? signal._entry.length
|
|
@@ -4288,7 +4288,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4288
4288
|
type: "profit",
|
|
4289
4289
|
percent: percentToClose,
|
|
4290
4290
|
entryCountAtClose,
|
|
4291
|
-
|
|
4291
|
+
currentPrice,
|
|
4292
4292
|
debugTimestamp: getDebugTimestamp(),
|
|
4293
4293
|
effectivePrice,
|
|
4294
4294
|
});
|
|
@@ -4327,7 +4327,7 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4327
4327
|
signal._partial.push({
|
|
4328
4328
|
type: "loss",
|
|
4329
4329
|
percent: percentToClose,
|
|
4330
|
-
|
|
4330
|
+
currentPrice,
|
|
4331
4331
|
entryCountAtClose,
|
|
4332
4332
|
effectivePrice,
|
|
4333
4333
|
debugTimestamp: getDebugTimestamp(),
|
|
@@ -6270,6 +6270,137 @@ class ClientStrategy {
|
|
|
6270
6270
|
const { remainingCostBasis } = getTotalClosed(this._pendingSignal);
|
|
6271
6271
|
return remainingCostBasis;
|
|
6272
6272
|
}
|
|
6273
|
+
/**
|
|
6274
|
+
* Returns the effective (DCA-averaged) entry price for the current pending signal.
|
|
6275
|
+
*
|
|
6276
|
+
* This is the harmonic mean of all _entry prices, which is the correct
|
|
6277
|
+
* cost-basis price used in all PNL calculations.
|
|
6278
|
+
* With no DCA entries, equals the original priceOpen.
|
|
6279
|
+
*
|
|
6280
|
+
* Returns null if no pending signal exists.
|
|
6281
|
+
*
|
|
6282
|
+
* @param symbol - Trading pair symbol
|
|
6283
|
+
* @returns Promise resolving to effective entry price or null
|
|
6284
|
+
*/
|
|
6285
|
+
async getPositionAveragePrice(symbol) {
|
|
6286
|
+
this.params.logger.debug("ClientStrategy getPositionAveragePrice", { symbol });
|
|
6287
|
+
if (!this._pendingSignal) {
|
|
6288
|
+
return null;
|
|
6289
|
+
}
|
|
6290
|
+
return getEffectivePriceOpen(this._pendingSignal);
|
|
6291
|
+
}
|
|
6292
|
+
/**
|
|
6293
|
+
* Returns the number of DCA entries made for the current pending signal.
|
|
6294
|
+
*
|
|
6295
|
+
* 1 = original entry only (no DCA).
|
|
6296
|
+
* Increases by 1 with each successful commitAverageBuy().
|
|
6297
|
+
*
|
|
6298
|
+
* Returns null if no pending signal exists.
|
|
6299
|
+
*
|
|
6300
|
+
* @param symbol - Trading pair symbol
|
|
6301
|
+
* @returns Promise resolving to entry count or null
|
|
6302
|
+
*/
|
|
6303
|
+
async getPositionInvestedCount(symbol) {
|
|
6304
|
+
this.params.logger.debug("ClientStrategy getPositionInvestedCount", { symbol });
|
|
6305
|
+
if (!this._pendingSignal) {
|
|
6306
|
+
return null;
|
|
6307
|
+
}
|
|
6308
|
+
return this._pendingSignal._entry?.length ?? 1;
|
|
6309
|
+
}
|
|
6310
|
+
/**
|
|
6311
|
+
* Returns the total invested cost basis in dollars for the current pending signal.
|
|
6312
|
+
*
|
|
6313
|
+
* Equal to entryCount × $100 (COST_BASIS_PER_ENTRY).
|
|
6314
|
+
* 1 entry = $100, 2 entries = $200, etc.
|
|
6315
|
+
*
|
|
6316
|
+
* Returns null if no pending signal exists.
|
|
6317
|
+
*
|
|
6318
|
+
* @param symbol - Trading pair symbol
|
|
6319
|
+
* @returns Promise resolving to total invested cost in dollars or null
|
|
6320
|
+
*/
|
|
6321
|
+
async getPositionInvestedCost(symbol) {
|
|
6322
|
+
this.params.logger.debug("ClientStrategy getPositionInvestedCost", { symbol });
|
|
6323
|
+
if (!this._pendingSignal) {
|
|
6324
|
+
return null;
|
|
6325
|
+
}
|
|
6326
|
+
return (this._pendingSignal._entry?.length ?? 1) * COST_BASIS_PER_ENTRY;
|
|
6327
|
+
}
|
|
6328
|
+
/**
|
|
6329
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
6330
|
+
*
|
|
6331
|
+
* Accounts for partial closes, DCA entries, slippage and fees
|
|
6332
|
+
* (delegates to toProfitLossDto).
|
|
6333
|
+
*
|
|
6334
|
+
* Returns null if no pending signal exists.
|
|
6335
|
+
*
|
|
6336
|
+
* @param symbol - Trading pair symbol
|
|
6337
|
+
* @param currentPrice - Current market price
|
|
6338
|
+
* @returns Promise resolving to pnlPercentage or null
|
|
6339
|
+
*/
|
|
6340
|
+
async getPositionPnlPercent(symbol, currentPrice) {
|
|
6341
|
+
this.params.logger.debug("ClientStrategy getPositionPnlPercent", { symbol, currentPrice });
|
|
6342
|
+
if (!this._pendingSignal) {
|
|
6343
|
+
return null;
|
|
6344
|
+
}
|
|
6345
|
+
const pnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
6346
|
+
return pnl.pnlPercentage;
|
|
6347
|
+
}
|
|
6348
|
+
/**
|
|
6349
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
6350
|
+
*
|
|
6351
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost
|
|
6352
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
6353
|
+
*
|
|
6354
|
+
* Returns null if no pending signal exists.
|
|
6355
|
+
*
|
|
6356
|
+
* @param symbol - Trading pair symbol
|
|
6357
|
+
* @param currentPrice - Current market price
|
|
6358
|
+
* @returns Promise resolving to pnl in dollars or null
|
|
6359
|
+
*/
|
|
6360
|
+
async getPositionPnlCost(symbol, currentPrice) {
|
|
6361
|
+
this.params.logger.debug("ClientStrategy getPositionPnlCost", { symbol, currentPrice });
|
|
6362
|
+
if (!this._pendingSignal) {
|
|
6363
|
+
return null;
|
|
6364
|
+
}
|
|
6365
|
+
const totalInvested = (this._pendingSignal._entry?.length ?? 1) * COST_BASIS_PER_ENTRY;
|
|
6366
|
+
const pnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
6367
|
+
return (pnl.pnlPercentage / 100) * totalInvested;
|
|
6368
|
+
}
|
|
6369
|
+
/**
|
|
6370
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
6371
|
+
*
|
|
6372
|
+
* The first element is always the original priceOpen (initial entry).
|
|
6373
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
6374
|
+
*
|
|
6375
|
+
* Returns null if no pending signal exists.
|
|
6376
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
6377
|
+
*
|
|
6378
|
+
* @param symbol - Trading pair symbol
|
|
6379
|
+
* @returns Promise resolving to array of entry prices or null
|
|
6380
|
+
*
|
|
6381
|
+
* @example
|
|
6382
|
+
* // No DCA: [43000]
|
|
6383
|
+
* // One DCA: [43000, 42000]
|
|
6384
|
+
* // Two DCA: [43000, 42000, 41500]
|
|
6385
|
+
*/
|
|
6386
|
+
async getPositionLevels(symbol) {
|
|
6387
|
+
this.params.logger.debug("ClientStrategy getPositionLevels", { symbol });
|
|
6388
|
+
if (!this._pendingSignal) {
|
|
6389
|
+
return null;
|
|
6390
|
+
}
|
|
6391
|
+
const entries = this._pendingSignal._entry;
|
|
6392
|
+
if (!entries || entries.length === 0) {
|
|
6393
|
+
return [this._pendingSignal.priceOpen];
|
|
6394
|
+
}
|
|
6395
|
+
return entries.map((e) => e.price);
|
|
6396
|
+
}
|
|
6397
|
+
async getPositionPartials(symbol) {
|
|
6398
|
+
this.params.logger.debug("ClientStrategy getPositionPartials", { symbol });
|
|
6399
|
+
if (!this._pendingSignal) {
|
|
6400
|
+
return null;
|
|
6401
|
+
}
|
|
6402
|
+
return this._pendingSignal._partial ?? [];
|
|
6403
|
+
}
|
|
6273
6404
|
/**
|
|
6274
6405
|
* Performs a single tick of strategy execution.
|
|
6275
6406
|
*
|
|
@@ -8445,6 +8576,71 @@ class StrategyConnectionService {
|
|
|
8445
8576
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8446
8577
|
return await strategy.getTotalCostClosed(symbol);
|
|
8447
8578
|
};
|
|
8579
|
+
this.getPositionAveragePrice = async (backtest, symbol, context) => {
|
|
8580
|
+
this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
|
|
8581
|
+
symbol,
|
|
8582
|
+
context,
|
|
8583
|
+
backtest,
|
|
8584
|
+
});
|
|
8585
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8586
|
+
return await strategy.getPositionAveragePrice(symbol);
|
|
8587
|
+
};
|
|
8588
|
+
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
8589
|
+
this.loggerService.log("strategyConnectionService getPositionInvestedCount", {
|
|
8590
|
+
symbol,
|
|
8591
|
+
context,
|
|
8592
|
+
backtest,
|
|
8593
|
+
});
|
|
8594
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8595
|
+
return await strategy.getPositionInvestedCount(symbol);
|
|
8596
|
+
};
|
|
8597
|
+
this.getPositionInvestedCost = async (backtest, symbol, context) => {
|
|
8598
|
+
this.loggerService.log("strategyConnectionService getPositionInvestedCost", {
|
|
8599
|
+
symbol,
|
|
8600
|
+
context,
|
|
8601
|
+
backtest,
|
|
8602
|
+
});
|
|
8603
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8604
|
+
return await strategy.getPositionInvestedCost(symbol);
|
|
8605
|
+
};
|
|
8606
|
+
this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
|
|
8607
|
+
this.loggerService.log("strategyConnectionService getPositionPnlPercent", {
|
|
8608
|
+
symbol,
|
|
8609
|
+
currentPrice,
|
|
8610
|
+
context,
|
|
8611
|
+
backtest,
|
|
8612
|
+
});
|
|
8613
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8614
|
+
return await strategy.getPositionPnlPercent(symbol, currentPrice);
|
|
8615
|
+
};
|
|
8616
|
+
this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
|
|
8617
|
+
this.loggerService.log("strategyConnectionService getPositionPnlCost", {
|
|
8618
|
+
symbol,
|
|
8619
|
+
currentPrice,
|
|
8620
|
+
context,
|
|
8621
|
+
backtest,
|
|
8622
|
+
});
|
|
8623
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8624
|
+
return await strategy.getPositionPnlCost(symbol, currentPrice);
|
|
8625
|
+
};
|
|
8626
|
+
this.getPositionLevels = async (backtest, symbol, context) => {
|
|
8627
|
+
this.loggerService.log("strategyConnectionService getPositionLevels", {
|
|
8628
|
+
symbol,
|
|
8629
|
+
context,
|
|
8630
|
+
backtest,
|
|
8631
|
+
});
|
|
8632
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8633
|
+
return await strategy.getPositionLevels(symbol);
|
|
8634
|
+
};
|
|
8635
|
+
this.getPositionPartials = async (backtest, symbol, context) => {
|
|
8636
|
+
this.loggerService.log("strategyConnectionService getPositionPartials", {
|
|
8637
|
+
symbol,
|
|
8638
|
+
context,
|
|
8639
|
+
backtest,
|
|
8640
|
+
});
|
|
8641
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8642
|
+
return await strategy.getPositionPartials(symbol);
|
|
8643
|
+
};
|
|
8448
8644
|
/**
|
|
8449
8645
|
* Retrieves the currently active scheduled signal for the strategy.
|
|
8450
8646
|
* If no scheduled signal exists, returns null.
|
|
@@ -11883,6 +12079,64 @@ class StrategyCoreService {
|
|
|
11883
12079
|
await this.validate(context);
|
|
11884
12080
|
return await this.strategyConnectionService.getTotalCostClosed(backtest, symbol, context);
|
|
11885
12081
|
};
|
|
12082
|
+
this.getPositionAveragePrice = async (backtest, symbol, context) => {
|
|
12083
|
+
this.loggerService.log("strategyCoreService getPositionAveragePrice", {
|
|
12084
|
+
symbol,
|
|
12085
|
+
context,
|
|
12086
|
+
});
|
|
12087
|
+
await this.validate(context);
|
|
12088
|
+
return await this.strategyConnectionService.getPositionAveragePrice(backtest, symbol, context);
|
|
12089
|
+
};
|
|
12090
|
+
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
12091
|
+
this.loggerService.log("strategyCoreService getPositionInvestedCount", {
|
|
12092
|
+
symbol,
|
|
12093
|
+
context,
|
|
12094
|
+
});
|
|
12095
|
+
await this.validate(context);
|
|
12096
|
+
return await this.strategyConnectionService.getPositionInvestedCount(backtest, symbol, context);
|
|
12097
|
+
};
|
|
12098
|
+
this.getPositionInvestedCost = async (backtest, symbol, context) => {
|
|
12099
|
+
this.loggerService.log("strategyCoreService getPositionInvestedCost", {
|
|
12100
|
+
symbol,
|
|
12101
|
+
context,
|
|
12102
|
+
});
|
|
12103
|
+
await this.validate(context);
|
|
12104
|
+
return await this.strategyConnectionService.getPositionInvestedCost(backtest, symbol, context);
|
|
12105
|
+
};
|
|
12106
|
+
this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
|
|
12107
|
+
this.loggerService.log("strategyCoreService getPositionPnlPercent", {
|
|
12108
|
+
symbol,
|
|
12109
|
+
currentPrice,
|
|
12110
|
+
context,
|
|
12111
|
+
});
|
|
12112
|
+
await this.validate(context);
|
|
12113
|
+
return await this.strategyConnectionService.getPositionPnlPercent(backtest, symbol, currentPrice, context);
|
|
12114
|
+
};
|
|
12115
|
+
this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
|
|
12116
|
+
this.loggerService.log("strategyCoreService getPositionPnlCost", {
|
|
12117
|
+
symbol,
|
|
12118
|
+
currentPrice,
|
|
12119
|
+
context,
|
|
12120
|
+
});
|
|
12121
|
+
await this.validate(context);
|
|
12122
|
+
return await this.strategyConnectionService.getPositionPnlCost(backtest, symbol, currentPrice, context);
|
|
12123
|
+
};
|
|
12124
|
+
this.getPositionLevels = async (backtest, symbol, context) => {
|
|
12125
|
+
this.loggerService.log("strategyCoreService getPositionLevels", {
|
|
12126
|
+
symbol,
|
|
12127
|
+
context,
|
|
12128
|
+
});
|
|
12129
|
+
await this.validate(context);
|
|
12130
|
+
return await this.strategyConnectionService.getPositionLevels(backtest, symbol, context);
|
|
12131
|
+
};
|
|
12132
|
+
this.getPositionPartials = async (backtest, symbol, context) => {
|
|
12133
|
+
this.loggerService.log("strategyCoreService getPositionPartials", {
|
|
12134
|
+
symbol,
|
|
12135
|
+
context,
|
|
12136
|
+
});
|
|
12137
|
+
await this.validate(context);
|
|
12138
|
+
return await this.strategyConnectionService.getPositionPartials(backtest, symbol, context);
|
|
12139
|
+
};
|
|
11886
12140
|
/**
|
|
11887
12141
|
* Retrieves the currently active scheduled signal for the symbol.
|
|
11888
12142
|
* If no scheduled signal exists, returns null.
|
|
@@ -28905,10 +29159,28 @@ async function getAggregatedTrades(symbol, limit) {
|
|
|
28905
29159
|
return await bt.exchangeConnectionService.getAggregatedTrades(symbol, limit);
|
|
28906
29160
|
}
|
|
28907
29161
|
|
|
29162
|
+
/**
|
|
29163
|
+
* Convert an absolute dollar amount to a percentage of the invested position cost.
|
|
29164
|
+
* Use the result as the `percent` argument to `commitPartialProfit` / `commitPartialLoss`.
|
|
29165
|
+
*
|
|
29166
|
+
* @param dollarAmount - Dollar value to close (e.g. 150)
|
|
29167
|
+
* @param investedCost - Total invested cost from `getPositionInvestedCost` (e.g. 300)
|
|
29168
|
+
* @returns Percentage of the position to close (0–100)
|
|
29169
|
+
*
|
|
29170
|
+
* @example
|
|
29171
|
+
* const percent = investedCostToPercent(150, 300); // 50
|
|
29172
|
+
* await commitPartialProfit("BTCUSDT", percent);
|
|
29173
|
+
*/
|
|
29174
|
+
const investedCostToPercent = (dollarAmount, investedCost) => {
|
|
29175
|
+
return (dollarAmount / investedCost) * 100;
|
|
29176
|
+
};
|
|
29177
|
+
|
|
28908
29178
|
const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
|
|
28909
29179
|
const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
|
|
28910
29180
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
28911
29181
|
const PARTIAL_LOSS_METHOD_NAME = "strategy.commitPartialLoss";
|
|
29182
|
+
const PARTIAL_PROFIT_COST_METHOD_NAME = "strategy.commitPartialProfitCost";
|
|
29183
|
+
const PARTIAL_LOSS_COST_METHOD_NAME = "strategy.commitPartialLossCost";
|
|
28912
29184
|
const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
|
|
28913
29185
|
const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
|
|
28914
29186
|
const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
|
|
@@ -28919,6 +29191,13 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
|
|
|
28919
29191
|
const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
|
|
28920
29192
|
const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
|
|
28921
29193
|
const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
|
|
29194
|
+
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionAveragePrice";
|
|
29195
|
+
const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
|
|
29196
|
+
const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
|
|
29197
|
+
const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
29198
|
+
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
29199
|
+
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
29200
|
+
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
28922
29201
|
/**
|
|
28923
29202
|
* Cancels the scheduled signal without stopping the strategy.
|
|
28924
29203
|
*
|
|
@@ -29477,6 +29756,205 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
29477
29756
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29478
29757
|
return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
29479
29758
|
}
|
|
29759
|
+
async function getPositionAveragePrice(symbol) {
|
|
29760
|
+
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, { symbol });
|
|
29761
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29762
|
+
throw new Error("getPositionAveragePrice requires an execution context");
|
|
29763
|
+
}
|
|
29764
|
+
if (!MethodContextService.hasContext()) {
|
|
29765
|
+
throw new Error("getPositionAveragePrice requires a method context");
|
|
29766
|
+
}
|
|
29767
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29768
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29769
|
+
return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29770
|
+
}
|
|
29771
|
+
async function getPositionInvestedCount(symbol) {
|
|
29772
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, { symbol });
|
|
29773
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29774
|
+
throw new Error("getPositionInvestedCount requires an execution context");
|
|
29775
|
+
}
|
|
29776
|
+
if (!MethodContextService.hasContext()) {
|
|
29777
|
+
throw new Error("getPositionInvestedCount requires a method context");
|
|
29778
|
+
}
|
|
29779
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29780
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29781
|
+
return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29782
|
+
}
|
|
29783
|
+
async function getPositionInvestedCost(symbol) {
|
|
29784
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, { symbol });
|
|
29785
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29786
|
+
throw new Error("getPositionInvestedCost requires an execution context");
|
|
29787
|
+
}
|
|
29788
|
+
if (!MethodContextService.hasContext()) {
|
|
29789
|
+
throw new Error("getPositionInvestedCost requires a method context");
|
|
29790
|
+
}
|
|
29791
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29792
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29793
|
+
return await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29794
|
+
}
|
|
29795
|
+
async function getPositionPnlPercent(symbol) {
|
|
29796
|
+
bt.loggerService.info(GET_POSITION_PNL_PERCENT_METHOD_NAME, { symbol });
|
|
29797
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29798
|
+
throw new Error("getPositionPnlPercent requires an execution context");
|
|
29799
|
+
}
|
|
29800
|
+
if (!MethodContextService.hasContext()) {
|
|
29801
|
+
throw new Error("getPositionPnlPercent requires a method context");
|
|
29802
|
+
}
|
|
29803
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29804
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29805
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29806
|
+
return await bt.strategyCoreService.getPositionPnlPercent(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
29807
|
+
}
|
|
29808
|
+
/**
|
|
29809
|
+
* Executes partial close at profit level by absolute dollar amount (moving toward TP).
|
|
29810
|
+
*
|
|
29811
|
+
* Convenience wrapper around commitPartialProfit that converts a dollar amount
|
|
29812
|
+
* to a percentage of the invested position cost automatically.
|
|
29813
|
+
* Price must be moving toward take profit (in profit direction).
|
|
29814
|
+
*
|
|
29815
|
+
* Automatically detects backtest/live mode from execution context.
|
|
29816
|
+
* Automatically fetches current price via getAveragePrice.
|
|
29817
|
+
*
|
|
29818
|
+
* @param symbol - Trading pair symbol
|
|
29819
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 150 closes $150 worth)
|
|
29820
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
29821
|
+
*
|
|
29822
|
+
* @throws Error if currentPrice is not in profit direction:
|
|
29823
|
+
* - LONG: currentPrice must be > priceOpen
|
|
29824
|
+
* - SHORT: currentPrice must be < priceOpen
|
|
29825
|
+
*
|
|
29826
|
+
* @example
|
|
29827
|
+
* ```typescript
|
|
29828
|
+
* import { commitPartialProfitCost } from "backtest-kit";
|
|
29829
|
+
*
|
|
29830
|
+
* // Close $150 of a $300 position (50%) at profit
|
|
29831
|
+
* const success = await commitPartialProfitCost("BTCUSDT", 150);
|
|
29832
|
+
* if (success) {
|
|
29833
|
+
* console.log('Partial profit executed');
|
|
29834
|
+
* }
|
|
29835
|
+
* ```
|
|
29836
|
+
*/
|
|
29837
|
+
async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
29838
|
+
bt.loggerService.info(PARTIAL_PROFIT_COST_METHOD_NAME, { symbol, dollarAmount });
|
|
29839
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29840
|
+
throw new Error("commitPartialProfitCost requires an execution context");
|
|
29841
|
+
}
|
|
29842
|
+
if (!MethodContextService.hasContext()) {
|
|
29843
|
+
throw new Error("commitPartialProfitCost requires a method context");
|
|
29844
|
+
}
|
|
29845
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29846
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29847
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29848
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29849
|
+
if (investedCost === null)
|
|
29850
|
+
return false;
|
|
29851
|
+
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
29852
|
+
return await bt.strategyCoreService.partialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
29853
|
+
}
|
|
29854
|
+
/**
|
|
29855
|
+
* Executes partial close at loss level by absolute dollar amount (moving toward SL).
|
|
29856
|
+
*
|
|
29857
|
+
* Convenience wrapper around commitPartialLoss that converts a dollar amount
|
|
29858
|
+
* to a percentage of the invested position cost automatically.
|
|
29859
|
+
* Price must be moving toward stop loss (in loss direction).
|
|
29860
|
+
*
|
|
29861
|
+
* Automatically detects backtest/live mode from execution context.
|
|
29862
|
+
* Automatically fetches current price via getAveragePrice.
|
|
29863
|
+
*
|
|
29864
|
+
* @param symbol - Trading pair symbol
|
|
29865
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 100 closes $100 worth)
|
|
29866
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
29867
|
+
*
|
|
29868
|
+
* @throws Error if currentPrice is not in loss direction:
|
|
29869
|
+
* - LONG: currentPrice must be < priceOpen
|
|
29870
|
+
* - SHORT: currentPrice must be > priceOpen
|
|
29871
|
+
*
|
|
29872
|
+
* @example
|
|
29873
|
+
* ```typescript
|
|
29874
|
+
* import { commitPartialLossCost } from "backtest-kit";
|
|
29875
|
+
*
|
|
29876
|
+
* // Close $100 of a $300 position (~33%) at loss
|
|
29877
|
+
* const success = await commitPartialLossCost("BTCUSDT", 100);
|
|
29878
|
+
* if (success) {
|
|
29879
|
+
* console.log('Partial loss executed');
|
|
29880
|
+
* }
|
|
29881
|
+
* ```
|
|
29882
|
+
*/
|
|
29883
|
+
async function commitPartialLossCost(symbol, dollarAmount) {
|
|
29884
|
+
bt.loggerService.info(PARTIAL_LOSS_COST_METHOD_NAME, { symbol, dollarAmount });
|
|
29885
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29886
|
+
throw new Error("commitPartialLossCost requires an execution context");
|
|
29887
|
+
}
|
|
29888
|
+
if (!MethodContextService.hasContext()) {
|
|
29889
|
+
throw new Error("commitPartialLossCost requires a method context");
|
|
29890
|
+
}
|
|
29891
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29892
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29893
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29894
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29895
|
+
if (investedCost === null)
|
|
29896
|
+
return false;
|
|
29897
|
+
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
29898
|
+
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
29899
|
+
}
|
|
29900
|
+
async function getPositionPnlCost(symbol) {
|
|
29901
|
+
bt.loggerService.info(GET_POSITION_PNL_COST_METHOD_NAME, { symbol });
|
|
29902
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29903
|
+
throw new Error("getPositionPnlCost requires an execution context");
|
|
29904
|
+
}
|
|
29905
|
+
if (!MethodContextService.hasContext()) {
|
|
29906
|
+
throw new Error("getPositionPnlCost requires a method context");
|
|
29907
|
+
}
|
|
29908
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29909
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29910
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29911
|
+
return await bt.strategyCoreService.getPositionPnlCost(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
29912
|
+
}
|
|
29913
|
+
/**
|
|
29914
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
29915
|
+
*
|
|
29916
|
+
* The first element is always the original priceOpen (initial entry).
|
|
29917
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
29918
|
+
*
|
|
29919
|
+
* Returns null if no pending signal exists.
|
|
29920
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
29921
|
+
*
|
|
29922
|
+
* @param symbol - Trading pair symbol
|
|
29923
|
+
* @returns Promise resolving to array of entry prices or null
|
|
29924
|
+
*
|
|
29925
|
+
* @example
|
|
29926
|
+
* ```typescript
|
|
29927
|
+
* import { getPositionLevels } from "backtest-kit";
|
|
29928
|
+
*
|
|
29929
|
+
* const levels = await getPositionLevels("BTCUSDT");
|
|
29930
|
+
* // No DCA: [43000]
|
|
29931
|
+
* // One DCA: [43000, 42000]
|
|
29932
|
+
* ```
|
|
29933
|
+
*/
|
|
29934
|
+
async function getPositionLevels(symbol) {
|
|
29935
|
+
bt.loggerService.info(GET_POSITION_LEVELS_METHOD_NAME, { symbol });
|
|
29936
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29937
|
+
throw new Error("getPositionLevels requires an execution context");
|
|
29938
|
+
}
|
|
29939
|
+
if (!MethodContextService.hasContext()) {
|
|
29940
|
+
throw new Error("getPositionLevels requires a method context");
|
|
29941
|
+
}
|
|
29942
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29943
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29944
|
+
return await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29945
|
+
}
|
|
29946
|
+
async function getPositionPartials(symbol) {
|
|
29947
|
+
bt.loggerService.info(GET_POSITION_PARTIALS_METHOD_NAME, { symbol });
|
|
29948
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29949
|
+
throw new Error("getPositionPartials requires an execution context");
|
|
29950
|
+
}
|
|
29951
|
+
if (!MethodContextService.hasContext()) {
|
|
29952
|
+
throw new Error("getPositionPartials requires a method context");
|
|
29953
|
+
}
|
|
29954
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29955
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29956
|
+
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29957
|
+
}
|
|
29480
29958
|
|
|
29481
29959
|
const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
|
|
29482
29960
|
/**
|
|
@@ -30668,11 +31146,20 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
|
|
|
30668
31146
|
const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
|
|
30669
31147
|
const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
|
|
30670
31148
|
const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
|
|
31149
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionAveragePrice";
|
|
31150
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
|
|
31151
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
|
|
31152
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
|
|
31153
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnlCost";
|
|
31154
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
31155
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
30671
31156
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
30672
31157
|
const BACKTEST_METHOD_NAME_CANCEL_SCHEDULED = "Backtest.commitCancelScheduled";
|
|
30673
31158
|
const BACKTEST_METHOD_NAME_CLOSE_PENDING = "Backtest.commitClosePending";
|
|
30674
31159
|
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.commitPartialProfit";
|
|
30675
31160
|
const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
|
|
31161
|
+
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST = "BacktestUtils.commitPartialProfitCost";
|
|
31162
|
+
const BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST = "BacktestUtils.commitPartialLossCost";
|
|
30676
31163
|
const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
|
|
30677
31164
|
const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
|
|
30678
31165
|
const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
|
|
@@ -31209,6 +31696,134 @@ class BacktestUtils {
|
|
|
31209
31696
|
}
|
|
31210
31697
|
return await bt.strategyCoreService.getBreakeven(true, symbol, currentPrice, context);
|
|
31211
31698
|
};
|
|
31699
|
+
this.getPositionAveragePrice = async (symbol, context) => {
|
|
31700
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
31701
|
+
symbol,
|
|
31702
|
+
context,
|
|
31703
|
+
});
|
|
31704
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
31705
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
31706
|
+
{
|
|
31707
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31708
|
+
riskName &&
|
|
31709
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
31710
|
+
riskList &&
|
|
31711
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
31712
|
+
actions &&
|
|
31713
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
31714
|
+
}
|
|
31715
|
+
return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
31716
|
+
};
|
|
31717
|
+
this.getPositionInvestedCount = async (symbol, context) => {
|
|
31718
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
31719
|
+
symbol,
|
|
31720
|
+
context,
|
|
31721
|
+
});
|
|
31722
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
31723
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
31724
|
+
{
|
|
31725
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31726
|
+
riskName &&
|
|
31727
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
31728
|
+
riskList &&
|
|
31729
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
31730
|
+
actions &&
|
|
31731
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
31732
|
+
}
|
|
31733
|
+
return await bt.strategyCoreService.getPositionInvestedCount(true, symbol, context);
|
|
31734
|
+
};
|
|
31735
|
+
this.getPositionInvestedCost = async (symbol, context) => {
|
|
31736
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
31737
|
+
symbol,
|
|
31738
|
+
context,
|
|
31739
|
+
});
|
|
31740
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
31741
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
31742
|
+
{
|
|
31743
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31744
|
+
riskName &&
|
|
31745
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
31746
|
+
riskList &&
|
|
31747
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
31748
|
+
actions &&
|
|
31749
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
31750
|
+
}
|
|
31751
|
+
return await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
31752
|
+
};
|
|
31753
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
31754
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
31755
|
+
symbol,
|
|
31756
|
+
currentPrice,
|
|
31757
|
+
context,
|
|
31758
|
+
});
|
|
31759
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
31760
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
31761
|
+
{
|
|
31762
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31763
|
+
riskName &&
|
|
31764
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
31765
|
+
riskList &&
|
|
31766
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
31767
|
+
actions &&
|
|
31768
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
31769
|
+
}
|
|
31770
|
+
return await bt.strategyCoreService.getPositionPnlPercent(true, symbol, currentPrice, context);
|
|
31771
|
+
};
|
|
31772
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
31773
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
31774
|
+
symbol,
|
|
31775
|
+
currentPrice,
|
|
31776
|
+
context,
|
|
31777
|
+
});
|
|
31778
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
31779
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
31780
|
+
{
|
|
31781
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31782
|
+
riskName &&
|
|
31783
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
31784
|
+
riskList &&
|
|
31785
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
31786
|
+
actions &&
|
|
31787
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
31788
|
+
}
|
|
31789
|
+
return await bt.strategyCoreService.getPositionPnlCost(true, symbol, currentPrice, context);
|
|
31790
|
+
};
|
|
31791
|
+
this.getPositionLevels = async (symbol, context) => {
|
|
31792
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
31793
|
+
symbol,
|
|
31794
|
+
context,
|
|
31795
|
+
});
|
|
31796
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS);
|
|
31797
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS);
|
|
31798
|
+
{
|
|
31799
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31800
|
+
riskName &&
|
|
31801
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS);
|
|
31802
|
+
riskList &&
|
|
31803
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS));
|
|
31804
|
+
actions &&
|
|
31805
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS));
|
|
31806
|
+
}
|
|
31807
|
+
return await bt.strategyCoreService.getPositionLevels(true, symbol, context);
|
|
31808
|
+
};
|
|
31809
|
+
this.getPositionPartials = async (symbol, context) => {
|
|
31810
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
31811
|
+
symbol,
|
|
31812
|
+
context,
|
|
31813
|
+
});
|
|
31814
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
31815
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
31816
|
+
{
|
|
31817
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31818
|
+
riskName &&
|
|
31819
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
31820
|
+
riskList &&
|
|
31821
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
31822
|
+
actions &&
|
|
31823
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
31824
|
+
}
|
|
31825
|
+
return await bt.strategyCoreService.getPositionPartials(true, symbol, context);
|
|
31826
|
+
};
|
|
31212
31827
|
/**
|
|
31213
31828
|
* Stops the strategy from generating new signals.
|
|
31214
31829
|
*
|
|
@@ -31430,6 +32045,114 @@ class BacktestUtils {
|
|
|
31430
32045
|
}
|
|
31431
32046
|
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
31432
32047
|
};
|
|
32048
|
+
/**
|
|
32049
|
+
* Executes partial close at profit level by absolute dollar amount (moving toward TP).
|
|
32050
|
+
*
|
|
32051
|
+
* Convenience wrapper around commitPartialProfit that converts a dollar amount
|
|
32052
|
+
* to a percentage of the invested position cost automatically.
|
|
32053
|
+
* Price must be moving toward take profit (in profit direction).
|
|
32054
|
+
*
|
|
32055
|
+
* @param symbol - Trading pair symbol
|
|
32056
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 150 closes $150 worth)
|
|
32057
|
+
* @param currentPrice - Current market price for this partial close
|
|
32058
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
32059
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
32060
|
+
*
|
|
32061
|
+
* @throws Error if currentPrice is not in profit direction:
|
|
32062
|
+
* - LONG: currentPrice must be > priceOpen
|
|
32063
|
+
* - SHORT: currentPrice must be < priceOpen
|
|
32064
|
+
*
|
|
32065
|
+
* @example
|
|
32066
|
+
* ```typescript
|
|
32067
|
+
* // Close $150 of a $300 position (50%) at profit
|
|
32068
|
+
* const success = await Backtest.commitPartialProfitCost("BTCUSDT", 150, 45000, {
|
|
32069
|
+
* exchangeName: "binance",
|
|
32070
|
+
* frameName: "frame1",
|
|
32071
|
+
* strategyName: "my-strategy"
|
|
32072
|
+
* });
|
|
32073
|
+
* if (success) {
|
|
32074
|
+
* console.log('Partial profit executed');
|
|
32075
|
+
* }
|
|
32076
|
+
* ```
|
|
32077
|
+
*/
|
|
32078
|
+
this.commitPartialProfitCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
32079
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST, {
|
|
32080
|
+
symbol,
|
|
32081
|
+
dollarAmount,
|
|
32082
|
+
currentPrice,
|
|
32083
|
+
context,
|
|
32084
|
+
});
|
|
32085
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
32086
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
32087
|
+
{
|
|
32088
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
32089
|
+
riskName &&
|
|
32090
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
32091
|
+
riskList &&
|
|
32092
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
32093
|
+
actions &&
|
|
32094
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
32095
|
+
}
|
|
32096
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
32097
|
+
if (investedCost === null)
|
|
32098
|
+
return false;
|
|
32099
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
32100
|
+
return await bt.strategyCoreService.partialProfit(true, symbol, percentToClose, currentPrice, context);
|
|
32101
|
+
};
|
|
32102
|
+
/**
|
|
32103
|
+
* Executes partial close at loss level by absolute dollar amount (moving toward SL).
|
|
32104
|
+
*
|
|
32105
|
+
* Convenience wrapper around commitPartialLoss that converts a dollar amount
|
|
32106
|
+
* to a percentage of the invested position cost automatically.
|
|
32107
|
+
* Price must be moving toward stop loss (in loss direction).
|
|
32108
|
+
*
|
|
32109
|
+
* @param symbol - Trading pair symbol
|
|
32110
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 100 closes $100 worth)
|
|
32111
|
+
* @param currentPrice - Current market price for this partial close
|
|
32112
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
32113
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
32114
|
+
*
|
|
32115
|
+
* @throws Error if currentPrice is not in loss direction:
|
|
32116
|
+
* - LONG: currentPrice must be < priceOpen
|
|
32117
|
+
* - SHORT: currentPrice must be > priceOpen
|
|
32118
|
+
*
|
|
32119
|
+
* @example
|
|
32120
|
+
* ```typescript
|
|
32121
|
+
* // Close $100 of a $300 position (~33%) at loss
|
|
32122
|
+
* const success = await Backtest.commitPartialLossCost("BTCUSDT", 100, 38000, {
|
|
32123
|
+
* exchangeName: "binance",
|
|
32124
|
+
* frameName: "frame1",
|
|
32125
|
+
* strategyName: "my-strategy"
|
|
32126
|
+
* });
|
|
32127
|
+
* if (success) {
|
|
32128
|
+
* console.log('Partial loss executed');
|
|
32129
|
+
* }
|
|
32130
|
+
* ```
|
|
32131
|
+
*/
|
|
32132
|
+
this.commitPartialLossCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
32133
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST, {
|
|
32134
|
+
symbol,
|
|
32135
|
+
dollarAmount,
|
|
32136
|
+
currentPrice,
|
|
32137
|
+
context,
|
|
32138
|
+
});
|
|
32139
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
32140
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
32141
|
+
{
|
|
32142
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
32143
|
+
riskName &&
|
|
32144
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
32145
|
+
riskList &&
|
|
32146
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
32147
|
+
actions &&
|
|
32148
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
32149
|
+
}
|
|
32150
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
32151
|
+
if (investedCost === null)
|
|
32152
|
+
return false;
|
|
32153
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
32154
|
+
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
32155
|
+
};
|
|
31433
32156
|
/**
|
|
31434
32157
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
31435
32158
|
*
|
|
@@ -31851,11 +32574,20 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
|
|
|
31851
32574
|
const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
|
|
31852
32575
|
const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
|
|
31853
32576
|
const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
|
|
32577
|
+
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionAveragePrice";
|
|
32578
|
+
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
|
|
32579
|
+
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
|
|
32580
|
+
const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
|
|
32581
|
+
const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
32582
|
+
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
32583
|
+
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
31854
32584
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
31855
32585
|
const LIVE_METHOD_NAME_CANCEL_SCHEDULED = "Live.cancelScheduled";
|
|
31856
32586
|
const LIVE_METHOD_NAME_CLOSE_PENDING = "Live.closePending";
|
|
31857
32587
|
const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.commitPartialProfit";
|
|
31858
32588
|
const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
|
|
32589
|
+
const LIVE_METHOD_NAME_PARTIAL_PROFIT_COST = "LiveUtils.commitPartialProfitCost";
|
|
32590
|
+
const LIVE_METHOD_NAME_PARTIAL_LOSS_COST = "LiveUtils.commitPartialLossCost";
|
|
31859
32591
|
const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
|
|
31860
32592
|
const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
|
|
31861
32593
|
const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
|
|
@@ -32364,6 +33096,118 @@ class LiveUtils {
|
|
|
32364
33096
|
frameName: "",
|
|
32365
33097
|
});
|
|
32366
33098
|
};
|
|
33099
|
+
this.getPositionAveragePrice = async (symbol, context) => {
|
|
33100
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, { symbol, context });
|
|
33101
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
33102
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
33103
|
+
{
|
|
33104
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33105
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
33106
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
33107
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
33108
|
+
}
|
|
33109
|
+
return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
33110
|
+
strategyName: context.strategyName,
|
|
33111
|
+
exchangeName: context.exchangeName,
|
|
33112
|
+
frameName: "",
|
|
33113
|
+
});
|
|
33114
|
+
};
|
|
33115
|
+
this.getPositionInvestedCount = async (symbol, context) => {
|
|
33116
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT, { symbol, context });
|
|
33117
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
33118
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
33119
|
+
{
|
|
33120
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33121
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
33122
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
33123
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
33124
|
+
}
|
|
33125
|
+
return await bt.strategyCoreService.getPositionInvestedCount(false, symbol, {
|
|
33126
|
+
strategyName: context.strategyName,
|
|
33127
|
+
exchangeName: context.exchangeName,
|
|
33128
|
+
frameName: "",
|
|
33129
|
+
});
|
|
33130
|
+
};
|
|
33131
|
+
this.getPositionInvestedCost = async (symbol, context) => {
|
|
33132
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST, { symbol, context });
|
|
33133
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
33134
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
33135
|
+
{
|
|
33136
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33137
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
33138
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
33139
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
33140
|
+
}
|
|
33141
|
+
return await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
33142
|
+
strategyName: context.strategyName,
|
|
33143
|
+
exchangeName: context.exchangeName,
|
|
33144
|
+
frameName: "",
|
|
33145
|
+
});
|
|
33146
|
+
};
|
|
33147
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
33148
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
|
|
33149
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
33150
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
33151
|
+
{
|
|
33152
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33153
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
33154
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
33155
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
33156
|
+
}
|
|
33157
|
+
return await bt.strategyCoreService.getPositionPnlPercent(false, symbol, currentPrice, {
|
|
33158
|
+
strategyName: context.strategyName,
|
|
33159
|
+
exchangeName: context.exchangeName,
|
|
33160
|
+
frameName: "",
|
|
33161
|
+
});
|
|
33162
|
+
};
|
|
33163
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
33164
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
|
|
33165
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
33166
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
33167
|
+
{
|
|
33168
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33169
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
33170
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
33171
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
33172
|
+
}
|
|
33173
|
+
return await bt.strategyCoreService.getPositionPnlCost(false, symbol, currentPrice, {
|
|
33174
|
+
strategyName: context.strategyName,
|
|
33175
|
+
exchangeName: context.exchangeName,
|
|
33176
|
+
frameName: "",
|
|
33177
|
+
});
|
|
33178
|
+
};
|
|
33179
|
+
this.getPositionLevels = async (symbol, context) => {
|
|
33180
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_LEVELS, { symbol, context });
|
|
33181
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
33182
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
33183
|
+
{
|
|
33184
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33185
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
33186
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
33187
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
33188
|
+
}
|
|
33189
|
+
return await bt.strategyCoreService.getPositionLevels(false, symbol, {
|
|
33190
|
+
strategyName: context.strategyName,
|
|
33191
|
+
exchangeName: context.exchangeName,
|
|
33192
|
+
frameName: "",
|
|
33193
|
+
});
|
|
33194
|
+
};
|
|
33195
|
+
this.getPositionPartials = async (symbol, context) => {
|
|
33196
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIALS, { symbol, context });
|
|
33197
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
33198
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
33199
|
+
{
|
|
33200
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33201
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
33202
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
33203
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
33204
|
+
}
|
|
33205
|
+
return await bt.strategyCoreService.getPositionPartials(false, symbol, {
|
|
33206
|
+
strategyName: context.strategyName,
|
|
33207
|
+
exchangeName: context.exchangeName,
|
|
33208
|
+
frameName: "",
|
|
33209
|
+
});
|
|
33210
|
+
};
|
|
32367
33211
|
/**
|
|
32368
33212
|
* Stops the strategy from generating new signals.
|
|
32369
33213
|
*
|
|
@@ -32582,6 +33426,112 @@ class LiveUtils {
|
|
|
32582
33426
|
frameName: "",
|
|
32583
33427
|
});
|
|
32584
33428
|
};
|
|
33429
|
+
/**
|
|
33430
|
+
* Executes partial close at profit level by absolute dollar amount (moving toward TP).
|
|
33431
|
+
*
|
|
33432
|
+
* Convenience wrapper around commitPartialProfit that converts a dollar amount
|
|
33433
|
+
* to a percentage of the invested position cost automatically.
|
|
33434
|
+
* Price must be moving toward take profit (in profit direction).
|
|
33435
|
+
*
|
|
33436
|
+
* @param symbol - Trading pair symbol
|
|
33437
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 150 closes $150 worth)
|
|
33438
|
+
* @param currentPrice - Current market price for this partial close
|
|
33439
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
33440
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
33441
|
+
*
|
|
33442
|
+
* @throws Error if currentPrice is not in profit direction:
|
|
33443
|
+
* - LONG: currentPrice must be > priceOpen
|
|
33444
|
+
* - SHORT: currentPrice must be < priceOpen
|
|
33445
|
+
*
|
|
33446
|
+
* @example
|
|
33447
|
+
* ```typescript
|
|
33448
|
+
* // Close $150 of a $300 position (50%) at profit
|
|
33449
|
+
* const success = await Live.commitPartialProfitCost("BTCUSDT", 150, 45000, {
|
|
33450
|
+
* exchangeName: "binance",
|
|
33451
|
+
* strategyName: "my-strategy"
|
|
33452
|
+
* });
|
|
33453
|
+
* if (success) {
|
|
33454
|
+
* console.log('Partial profit executed');
|
|
33455
|
+
* }
|
|
33456
|
+
* ```
|
|
33457
|
+
*/
|
|
33458
|
+
this.commitPartialProfitCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
33459
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_PROFIT_COST, { symbol, dollarAmount, currentPrice, context });
|
|
33460
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
33461
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
33462
|
+
{
|
|
33463
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33464
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
33465
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
33466
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
33467
|
+
}
|
|
33468
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
33469
|
+
strategyName: context.strategyName,
|
|
33470
|
+
exchangeName: context.exchangeName,
|
|
33471
|
+
frameName: "",
|
|
33472
|
+
});
|
|
33473
|
+
if (investedCost === null)
|
|
33474
|
+
return false;
|
|
33475
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
33476
|
+
return await bt.strategyCoreService.partialProfit(false, symbol, percentToClose, currentPrice, {
|
|
33477
|
+
strategyName: context.strategyName,
|
|
33478
|
+
exchangeName: context.exchangeName,
|
|
33479
|
+
frameName: "",
|
|
33480
|
+
});
|
|
33481
|
+
};
|
|
33482
|
+
/**
|
|
33483
|
+
* Executes partial close at loss level by absolute dollar amount (moving toward SL).
|
|
33484
|
+
*
|
|
33485
|
+
* Convenience wrapper around commitPartialLoss that converts a dollar amount
|
|
33486
|
+
* to a percentage of the invested position cost automatically.
|
|
33487
|
+
* Price must be moving toward stop loss (in loss direction).
|
|
33488
|
+
*
|
|
33489
|
+
* @param symbol - Trading pair symbol
|
|
33490
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 100 closes $100 worth)
|
|
33491
|
+
* @param currentPrice - Current market price for this partial close
|
|
33492
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
33493
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
33494
|
+
*
|
|
33495
|
+
* @throws Error if currentPrice is not in loss direction:
|
|
33496
|
+
* - LONG: currentPrice must be < priceOpen
|
|
33497
|
+
* - SHORT: currentPrice must be > priceOpen
|
|
33498
|
+
*
|
|
33499
|
+
* @example
|
|
33500
|
+
* ```typescript
|
|
33501
|
+
* // Close $100 of a $300 position (~33%) at loss
|
|
33502
|
+
* const success = await Live.commitPartialLossCost("BTCUSDT", 100, 38000, {
|
|
33503
|
+
* exchangeName: "binance",
|
|
33504
|
+
* strategyName: "my-strategy"
|
|
33505
|
+
* });
|
|
33506
|
+
* if (success) {
|
|
33507
|
+
* console.log('Partial loss executed');
|
|
33508
|
+
* }
|
|
33509
|
+
* ```
|
|
33510
|
+
*/
|
|
33511
|
+
this.commitPartialLossCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
33512
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_LOSS_COST, { symbol, dollarAmount, currentPrice, context });
|
|
33513
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
33514
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
33515
|
+
{
|
|
33516
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33517
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
33518
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
33519
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
33520
|
+
}
|
|
33521
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
33522
|
+
strategyName: context.strategyName,
|
|
33523
|
+
exchangeName: context.exchangeName,
|
|
33524
|
+
frameName: "",
|
|
33525
|
+
});
|
|
33526
|
+
if (investedCost === null)
|
|
33527
|
+
return false;
|
|
33528
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
33529
|
+
return await bt.strategyCoreService.partialLoss(false, symbol, percentToClose, currentPrice, {
|
|
33530
|
+
strategyName: context.strategyName,
|
|
33531
|
+
exchangeName: context.exchangeName,
|
|
33532
|
+
frameName: "",
|
|
33533
|
+
});
|
|
33534
|
+
};
|
|
32585
33535
|
/**
|
|
32586
33536
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
32587
33537
|
*
|
|
@@ -40102,6 +41052,27 @@ const set = (object, path, value) => {
|
|
|
40102
41052
|
}
|
|
40103
41053
|
};
|
|
40104
41054
|
|
|
41055
|
+
/**
|
|
41056
|
+
* Calculate the percentage difference between two numbers.
|
|
41057
|
+
* @param {number} a - The first number.
|
|
41058
|
+
* @param {number} b - The second number.
|
|
41059
|
+
* @returns {number} The percentage difference between the two numbers.
|
|
41060
|
+
*/
|
|
41061
|
+
const percentDiff = (a = 1, b = 2) => {
|
|
41062
|
+
const result = 100 / (Math.min(a, b) / Math.max(a, b)) - 100;
|
|
41063
|
+
return Number.isFinite(result) ? result : 100;
|
|
41064
|
+
};
|
|
41065
|
+
|
|
41066
|
+
/**
|
|
41067
|
+
* Calculate the percentage change from yesterday's value to today's value.
|
|
41068
|
+
* @param {number} yesterdayValue - The value from yesterday.
|
|
41069
|
+
* @param {number} todayValue - The value from today.
|
|
41070
|
+
* @returns {number} The percentage change from yesterday to today.
|
|
41071
|
+
*/
|
|
41072
|
+
const percentValue = (yesterdayValue, todayValue) => {
|
|
41073
|
+
return yesterdayValue / todayValue - 1;
|
|
41074
|
+
};
|
|
41075
|
+
|
|
40105
41076
|
exports.ActionBase = ActionBase;
|
|
40106
41077
|
exports.Backtest = Backtest;
|
|
40107
41078
|
exports.Breakeven = Breakeven;
|
|
@@ -40157,7 +41128,9 @@ exports.commitBreakeven = commitBreakeven;
|
|
|
40157
41128
|
exports.commitCancelScheduled = commitCancelScheduled;
|
|
40158
41129
|
exports.commitClosePending = commitClosePending;
|
|
40159
41130
|
exports.commitPartialLoss = commitPartialLoss;
|
|
41131
|
+
exports.commitPartialLossCost = commitPartialLossCost;
|
|
40160
41132
|
exports.commitPartialProfit = commitPartialProfit;
|
|
41133
|
+
exports.commitPartialProfitCost = commitPartialProfitCost;
|
|
40161
41134
|
exports.commitTrailingStop = commitTrailingStop;
|
|
40162
41135
|
exports.commitTrailingTake = commitTrailingTake;
|
|
40163
41136
|
exports.dumpMessages = dumpMessages;
|
|
@@ -40184,6 +41157,13 @@ exports.getMode = getMode;
|
|
|
40184
41157
|
exports.getNextCandles = getNextCandles;
|
|
40185
41158
|
exports.getOrderBook = getOrderBook;
|
|
40186
41159
|
exports.getPendingSignal = getPendingSignal;
|
|
41160
|
+
exports.getPositionAveragePrice = getPositionAveragePrice;
|
|
41161
|
+
exports.getPositionInvestedCost = getPositionInvestedCost;
|
|
41162
|
+
exports.getPositionInvestedCount = getPositionInvestedCount;
|
|
41163
|
+
exports.getPositionLevels = getPositionLevels;
|
|
41164
|
+
exports.getPositionPartials = getPositionPartials;
|
|
41165
|
+
exports.getPositionPnlCost = getPositionPnlCost;
|
|
41166
|
+
exports.getPositionPnlPercent = getPositionPnlPercent;
|
|
40187
41167
|
exports.getRawCandles = getRawCandles;
|
|
40188
41168
|
exports.getRiskSchema = getRiskSchema;
|
|
40189
41169
|
exports.getScheduledSignal = getScheduledSignal;
|
|
@@ -40196,6 +41176,7 @@ exports.getTotalCostClosed = getTotalCostClosed;
|
|
|
40196
41176
|
exports.getTotalPercentClosed = getTotalPercentClosed;
|
|
40197
41177
|
exports.getWalkerSchema = getWalkerSchema;
|
|
40198
41178
|
exports.hasTradeContext = hasTradeContext;
|
|
41179
|
+
exports.investedCostToPercent = investedCostToPercent;
|
|
40199
41180
|
exports.lib = backtest;
|
|
40200
41181
|
exports.listExchangeSchema = listExchangeSchema;
|
|
40201
41182
|
exports.listFrameSchema = listFrameSchema;
|
|
@@ -40246,6 +41227,8 @@ exports.overrideSizingSchema = overrideSizingSchema;
|
|
|
40246
41227
|
exports.overrideStrategySchema = overrideStrategySchema;
|
|
40247
41228
|
exports.overrideWalkerSchema = overrideWalkerSchema;
|
|
40248
41229
|
exports.parseArgs = parseArgs;
|
|
41230
|
+
exports.percentDiff = percentDiff;
|
|
41231
|
+
exports.percentValue = percentValue;
|
|
40249
41232
|
exports.roundTicks = roundTicks;
|
|
40250
41233
|
exports.set = set;
|
|
40251
41234
|
exports.setColumns = setColumns;
|