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.mjs
CHANGED
|
@@ -3298,8 +3298,8 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
3298
3298
|
? partial.effectivePrice * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
3299
3299
|
: partial.effectivePrice * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3300
3300
|
const priceCloseWithSlippage = signal.position === "long"
|
|
3301
|
-
? partial.
|
|
3302
|
-
: partial.
|
|
3301
|
+
? partial.currentPrice * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
3302
|
+
: partial.currentPrice * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3303
3303
|
const partialPnl = signal.position === "long"
|
|
3304
3304
|
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
3305
3305
|
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
@@ -3784,8 +3784,8 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3784
3784
|
const TO_PUBLIC_SIGNAL = (signal) => {
|
|
3785
3785
|
const hasTrailingSL = "_trailingPriceStopLoss" in signal && signal._trailingPriceStopLoss !== undefined;
|
|
3786
3786
|
const hasTrailingTP = "_trailingPriceTakeProfit" in signal && signal._trailingPriceTakeProfit !== undefined;
|
|
3787
|
-
const partialExecuted =
|
|
3788
|
-
? signal
|
|
3787
|
+
const partialExecuted = "_partial" in signal
|
|
3788
|
+
? getTotalClosed(signal).totalClosedPercent
|
|
3789
3789
|
: 0;
|
|
3790
3790
|
const totalEntries = ("_entry" in signal && Array.isArray(signal._entry))
|
|
3791
3791
|
? signal._entry.length
|
|
@@ -4268,7 +4268,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4268
4268
|
type: "profit",
|
|
4269
4269
|
percent: percentToClose,
|
|
4270
4270
|
entryCountAtClose,
|
|
4271
|
-
|
|
4271
|
+
currentPrice,
|
|
4272
4272
|
debugTimestamp: getDebugTimestamp(),
|
|
4273
4273
|
effectivePrice,
|
|
4274
4274
|
});
|
|
@@ -4307,7 +4307,7 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4307
4307
|
signal._partial.push({
|
|
4308
4308
|
type: "loss",
|
|
4309
4309
|
percent: percentToClose,
|
|
4310
|
-
|
|
4310
|
+
currentPrice,
|
|
4311
4311
|
entryCountAtClose,
|
|
4312
4312
|
effectivePrice,
|
|
4313
4313
|
debugTimestamp: getDebugTimestamp(),
|
|
@@ -6250,6 +6250,137 @@ class ClientStrategy {
|
|
|
6250
6250
|
const { remainingCostBasis } = getTotalClosed(this._pendingSignal);
|
|
6251
6251
|
return remainingCostBasis;
|
|
6252
6252
|
}
|
|
6253
|
+
/**
|
|
6254
|
+
* Returns the effective (DCA-averaged) entry price for the current pending signal.
|
|
6255
|
+
*
|
|
6256
|
+
* This is the harmonic mean of all _entry prices, which is the correct
|
|
6257
|
+
* cost-basis price used in all PNL calculations.
|
|
6258
|
+
* With no DCA entries, equals the original priceOpen.
|
|
6259
|
+
*
|
|
6260
|
+
* Returns null if no pending signal exists.
|
|
6261
|
+
*
|
|
6262
|
+
* @param symbol - Trading pair symbol
|
|
6263
|
+
* @returns Promise resolving to effective entry price or null
|
|
6264
|
+
*/
|
|
6265
|
+
async getPositionAveragePrice(symbol) {
|
|
6266
|
+
this.params.logger.debug("ClientStrategy getPositionAveragePrice", { symbol });
|
|
6267
|
+
if (!this._pendingSignal) {
|
|
6268
|
+
return null;
|
|
6269
|
+
}
|
|
6270
|
+
return getEffectivePriceOpen(this._pendingSignal);
|
|
6271
|
+
}
|
|
6272
|
+
/**
|
|
6273
|
+
* Returns the number of DCA entries made for the current pending signal.
|
|
6274
|
+
*
|
|
6275
|
+
* 1 = original entry only (no DCA).
|
|
6276
|
+
* Increases by 1 with each successful commitAverageBuy().
|
|
6277
|
+
*
|
|
6278
|
+
* Returns null if no pending signal exists.
|
|
6279
|
+
*
|
|
6280
|
+
* @param symbol - Trading pair symbol
|
|
6281
|
+
* @returns Promise resolving to entry count or null
|
|
6282
|
+
*/
|
|
6283
|
+
async getPositionInvestedCount(symbol) {
|
|
6284
|
+
this.params.logger.debug("ClientStrategy getPositionInvestedCount", { symbol });
|
|
6285
|
+
if (!this._pendingSignal) {
|
|
6286
|
+
return null;
|
|
6287
|
+
}
|
|
6288
|
+
return this._pendingSignal._entry?.length ?? 1;
|
|
6289
|
+
}
|
|
6290
|
+
/**
|
|
6291
|
+
* Returns the total invested cost basis in dollars for the current pending signal.
|
|
6292
|
+
*
|
|
6293
|
+
* Equal to entryCount × $100 (COST_BASIS_PER_ENTRY).
|
|
6294
|
+
* 1 entry = $100, 2 entries = $200, etc.
|
|
6295
|
+
*
|
|
6296
|
+
* Returns null if no pending signal exists.
|
|
6297
|
+
*
|
|
6298
|
+
* @param symbol - Trading pair symbol
|
|
6299
|
+
* @returns Promise resolving to total invested cost in dollars or null
|
|
6300
|
+
*/
|
|
6301
|
+
async getPositionInvestedCost(symbol) {
|
|
6302
|
+
this.params.logger.debug("ClientStrategy getPositionInvestedCost", { symbol });
|
|
6303
|
+
if (!this._pendingSignal) {
|
|
6304
|
+
return null;
|
|
6305
|
+
}
|
|
6306
|
+
return (this._pendingSignal._entry?.length ?? 1) * COST_BASIS_PER_ENTRY;
|
|
6307
|
+
}
|
|
6308
|
+
/**
|
|
6309
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
6310
|
+
*
|
|
6311
|
+
* Accounts for partial closes, DCA entries, slippage and fees
|
|
6312
|
+
* (delegates to toProfitLossDto).
|
|
6313
|
+
*
|
|
6314
|
+
* Returns null if no pending signal exists.
|
|
6315
|
+
*
|
|
6316
|
+
* @param symbol - Trading pair symbol
|
|
6317
|
+
* @param currentPrice - Current market price
|
|
6318
|
+
* @returns Promise resolving to pnlPercentage or null
|
|
6319
|
+
*/
|
|
6320
|
+
async getPositionPnlPercent(symbol, currentPrice) {
|
|
6321
|
+
this.params.logger.debug("ClientStrategy getPositionPnlPercent", { symbol, currentPrice });
|
|
6322
|
+
if (!this._pendingSignal) {
|
|
6323
|
+
return null;
|
|
6324
|
+
}
|
|
6325
|
+
const pnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
6326
|
+
return pnl.pnlPercentage;
|
|
6327
|
+
}
|
|
6328
|
+
/**
|
|
6329
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
6330
|
+
*
|
|
6331
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost
|
|
6332
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
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 pnl in dollars or null
|
|
6339
|
+
*/
|
|
6340
|
+
async getPositionPnlCost(symbol, currentPrice) {
|
|
6341
|
+
this.params.logger.debug("ClientStrategy getPositionPnlCost", { symbol, currentPrice });
|
|
6342
|
+
if (!this._pendingSignal) {
|
|
6343
|
+
return null;
|
|
6344
|
+
}
|
|
6345
|
+
const totalInvested = (this._pendingSignal._entry?.length ?? 1) * COST_BASIS_PER_ENTRY;
|
|
6346
|
+
const pnl = toProfitLossDto(this._pendingSignal, currentPrice);
|
|
6347
|
+
return (pnl.pnlPercentage / 100) * totalInvested;
|
|
6348
|
+
}
|
|
6349
|
+
/**
|
|
6350
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
6351
|
+
*
|
|
6352
|
+
* The first element is always the original priceOpen (initial entry).
|
|
6353
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
6354
|
+
*
|
|
6355
|
+
* Returns null if no pending signal exists.
|
|
6356
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
6357
|
+
*
|
|
6358
|
+
* @param symbol - Trading pair symbol
|
|
6359
|
+
* @returns Promise resolving to array of entry prices or null
|
|
6360
|
+
*
|
|
6361
|
+
* @example
|
|
6362
|
+
* // No DCA: [43000]
|
|
6363
|
+
* // One DCA: [43000, 42000]
|
|
6364
|
+
* // Two DCA: [43000, 42000, 41500]
|
|
6365
|
+
*/
|
|
6366
|
+
async getPositionLevels(symbol) {
|
|
6367
|
+
this.params.logger.debug("ClientStrategy getPositionLevels", { symbol });
|
|
6368
|
+
if (!this._pendingSignal) {
|
|
6369
|
+
return null;
|
|
6370
|
+
}
|
|
6371
|
+
const entries = this._pendingSignal._entry;
|
|
6372
|
+
if (!entries || entries.length === 0) {
|
|
6373
|
+
return [this._pendingSignal.priceOpen];
|
|
6374
|
+
}
|
|
6375
|
+
return entries.map((e) => e.price);
|
|
6376
|
+
}
|
|
6377
|
+
async getPositionPartials(symbol) {
|
|
6378
|
+
this.params.logger.debug("ClientStrategy getPositionPartials", { symbol });
|
|
6379
|
+
if (!this._pendingSignal) {
|
|
6380
|
+
return null;
|
|
6381
|
+
}
|
|
6382
|
+
return this._pendingSignal._partial ?? [];
|
|
6383
|
+
}
|
|
6253
6384
|
/**
|
|
6254
6385
|
* Performs a single tick of strategy execution.
|
|
6255
6386
|
*
|
|
@@ -8425,6 +8556,71 @@ class StrategyConnectionService {
|
|
|
8425
8556
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8426
8557
|
return await strategy.getTotalCostClosed(symbol);
|
|
8427
8558
|
};
|
|
8559
|
+
this.getPositionAveragePrice = async (backtest, symbol, context) => {
|
|
8560
|
+
this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
|
|
8561
|
+
symbol,
|
|
8562
|
+
context,
|
|
8563
|
+
backtest,
|
|
8564
|
+
});
|
|
8565
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8566
|
+
return await strategy.getPositionAveragePrice(symbol);
|
|
8567
|
+
};
|
|
8568
|
+
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
8569
|
+
this.loggerService.log("strategyConnectionService getPositionInvestedCount", {
|
|
8570
|
+
symbol,
|
|
8571
|
+
context,
|
|
8572
|
+
backtest,
|
|
8573
|
+
});
|
|
8574
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8575
|
+
return await strategy.getPositionInvestedCount(symbol);
|
|
8576
|
+
};
|
|
8577
|
+
this.getPositionInvestedCost = async (backtest, symbol, context) => {
|
|
8578
|
+
this.loggerService.log("strategyConnectionService getPositionInvestedCost", {
|
|
8579
|
+
symbol,
|
|
8580
|
+
context,
|
|
8581
|
+
backtest,
|
|
8582
|
+
});
|
|
8583
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8584
|
+
return await strategy.getPositionInvestedCost(symbol);
|
|
8585
|
+
};
|
|
8586
|
+
this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
|
|
8587
|
+
this.loggerService.log("strategyConnectionService getPositionPnlPercent", {
|
|
8588
|
+
symbol,
|
|
8589
|
+
currentPrice,
|
|
8590
|
+
context,
|
|
8591
|
+
backtest,
|
|
8592
|
+
});
|
|
8593
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8594
|
+
return await strategy.getPositionPnlPercent(symbol, currentPrice);
|
|
8595
|
+
};
|
|
8596
|
+
this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
|
|
8597
|
+
this.loggerService.log("strategyConnectionService getPositionPnlCost", {
|
|
8598
|
+
symbol,
|
|
8599
|
+
currentPrice,
|
|
8600
|
+
context,
|
|
8601
|
+
backtest,
|
|
8602
|
+
});
|
|
8603
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8604
|
+
return await strategy.getPositionPnlCost(symbol, currentPrice);
|
|
8605
|
+
};
|
|
8606
|
+
this.getPositionLevels = async (backtest, symbol, context) => {
|
|
8607
|
+
this.loggerService.log("strategyConnectionService getPositionLevels", {
|
|
8608
|
+
symbol,
|
|
8609
|
+
context,
|
|
8610
|
+
backtest,
|
|
8611
|
+
});
|
|
8612
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8613
|
+
return await strategy.getPositionLevels(symbol);
|
|
8614
|
+
};
|
|
8615
|
+
this.getPositionPartials = async (backtest, symbol, context) => {
|
|
8616
|
+
this.loggerService.log("strategyConnectionService getPositionPartials", {
|
|
8617
|
+
symbol,
|
|
8618
|
+
context,
|
|
8619
|
+
backtest,
|
|
8620
|
+
});
|
|
8621
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8622
|
+
return await strategy.getPositionPartials(symbol);
|
|
8623
|
+
};
|
|
8428
8624
|
/**
|
|
8429
8625
|
* Retrieves the currently active scheduled signal for the strategy.
|
|
8430
8626
|
* If no scheduled signal exists, returns null.
|
|
@@ -11863,6 +12059,64 @@ class StrategyCoreService {
|
|
|
11863
12059
|
await this.validate(context);
|
|
11864
12060
|
return await this.strategyConnectionService.getTotalCostClosed(backtest, symbol, context);
|
|
11865
12061
|
};
|
|
12062
|
+
this.getPositionAveragePrice = async (backtest, symbol, context) => {
|
|
12063
|
+
this.loggerService.log("strategyCoreService getPositionAveragePrice", {
|
|
12064
|
+
symbol,
|
|
12065
|
+
context,
|
|
12066
|
+
});
|
|
12067
|
+
await this.validate(context);
|
|
12068
|
+
return await this.strategyConnectionService.getPositionAveragePrice(backtest, symbol, context);
|
|
12069
|
+
};
|
|
12070
|
+
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
12071
|
+
this.loggerService.log("strategyCoreService getPositionInvestedCount", {
|
|
12072
|
+
symbol,
|
|
12073
|
+
context,
|
|
12074
|
+
});
|
|
12075
|
+
await this.validate(context);
|
|
12076
|
+
return await this.strategyConnectionService.getPositionInvestedCount(backtest, symbol, context);
|
|
12077
|
+
};
|
|
12078
|
+
this.getPositionInvestedCost = async (backtest, symbol, context) => {
|
|
12079
|
+
this.loggerService.log("strategyCoreService getPositionInvestedCost", {
|
|
12080
|
+
symbol,
|
|
12081
|
+
context,
|
|
12082
|
+
});
|
|
12083
|
+
await this.validate(context);
|
|
12084
|
+
return await this.strategyConnectionService.getPositionInvestedCost(backtest, symbol, context);
|
|
12085
|
+
};
|
|
12086
|
+
this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
|
|
12087
|
+
this.loggerService.log("strategyCoreService getPositionPnlPercent", {
|
|
12088
|
+
symbol,
|
|
12089
|
+
currentPrice,
|
|
12090
|
+
context,
|
|
12091
|
+
});
|
|
12092
|
+
await this.validate(context);
|
|
12093
|
+
return await this.strategyConnectionService.getPositionPnlPercent(backtest, symbol, currentPrice, context);
|
|
12094
|
+
};
|
|
12095
|
+
this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
|
|
12096
|
+
this.loggerService.log("strategyCoreService getPositionPnlCost", {
|
|
12097
|
+
symbol,
|
|
12098
|
+
currentPrice,
|
|
12099
|
+
context,
|
|
12100
|
+
});
|
|
12101
|
+
await this.validate(context);
|
|
12102
|
+
return await this.strategyConnectionService.getPositionPnlCost(backtest, symbol, currentPrice, context);
|
|
12103
|
+
};
|
|
12104
|
+
this.getPositionLevels = async (backtest, symbol, context) => {
|
|
12105
|
+
this.loggerService.log("strategyCoreService getPositionLevels", {
|
|
12106
|
+
symbol,
|
|
12107
|
+
context,
|
|
12108
|
+
});
|
|
12109
|
+
await this.validate(context);
|
|
12110
|
+
return await this.strategyConnectionService.getPositionLevels(backtest, symbol, context);
|
|
12111
|
+
};
|
|
12112
|
+
this.getPositionPartials = async (backtest, symbol, context) => {
|
|
12113
|
+
this.loggerService.log("strategyCoreService getPositionPartials", {
|
|
12114
|
+
symbol,
|
|
12115
|
+
context,
|
|
12116
|
+
});
|
|
12117
|
+
await this.validate(context);
|
|
12118
|
+
return await this.strategyConnectionService.getPositionPartials(backtest, symbol, context);
|
|
12119
|
+
};
|
|
11866
12120
|
/**
|
|
11867
12121
|
* Retrieves the currently active scheduled signal for the symbol.
|
|
11868
12122
|
* If no scheduled signal exists, returns null.
|
|
@@ -28885,10 +29139,28 @@ async function getAggregatedTrades(symbol, limit) {
|
|
|
28885
29139
|
return await bt.exchangeConnectionService.getAggregatedTrades(symbol, limit);
|
|
28886
29140
|
}
|
|
28887
29141
|
|
|
29142
|
+
/**
|
|
29143
|
+
* Convert an absolute dollar amount to a percentage of the invested position cost.
|
|
29144
|
+
* Use the result as the `percent` argument to `commitPartialProfit` / `commitPartialLoss`.
|
|
29145
|
+
*
|
|
29146
|
+
* @param dollarAmount - Dollar value to close (e.g. 150)
|
|
29147
|
+
* @param investedCost - Total invested cost from `getPositionInvestedCost` (e.g. 300)
|
|
29148
|
+
* @returns Percentage of the position to close (0–100)
|
|
29149
|
+
*
|
|
29150
|
+
* @example
|
|
29151
|
+
* const percent = investedCostToPercent(150, 300); // 50
|
|
29152
|
+
* await commitPartialProfit("BTCUSDT", percent);
|
|
29153
|
+
*/
|
|
29154
|
+
const investedCostToPercent = (dollarAmount, investedCost) => {
|
|
29155
|
+
return (dollarAmount / investedCost) * 100;
|
|
29156
|
+
};
|
|
29157
|
+
|
|
28888
29158
|
const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
|
|
28889
29159
|
const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
|
|
28890
29160
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
28891
29161
|
const PARTIAL_LOSS_METHOD_NAME = "strategy.commitPartialLoss";
|
|
29162
|
+
const PARTIAL_PROFIT_COST_METHOD_NAME = "strategy.commitPartialProfitCost";
|
|
29163
|
+
const PARTIAL_LOSS_COST_METHOD_NAME = "strategy.commitPartialLossCost";
|
|
28892
29164
|
const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
|
|
28893
29165
|
const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
|
|
28894
29166
|
const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
|
|
@@ -28899,6 +29171,13 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
|
|
|
28899
29171
|
const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
|
|
28900
29172
|
const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
|
|
28901
29173
|
const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
|
|
29174
|
+
const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionAveragePrice";
|
|
29175
|
+
const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
|
|
29176
|
+
const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
|
|
29177
|
+
const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
29178
|
+
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
29179
|
+
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
29180
|
+
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
28902
29181
|
/**
|
|
28903
29182
|
* Cancels the scheduled signal without stopping the strategy.
|
|
28904
29183
|
*
|
|
@@ -29457,6 +29736,205 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
29457
29736
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29458
29737
|
return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
29459
29738
|
}
|
|
29739
|
+
async function getPositionAveragePrice(symbol) {
|
|
29740
|
+
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, { symbol });
|
|
29741
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29742
|
+
throw new Error("getPositionAveragePrice requires an execution context");
|
|
29743
|
+
}
|
|
29744
|
+
if (!MethodContextService.hasContext()) {
|
|
29745
|
+
throw new Error("getPositionAveragePrice requires a method context");
|
|
29746
|
+
}
|
|
29747
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29748
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29749
|
+
return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29750
|
+
}
|
|
29751
|
+
async function getPositionInvestedCount(symbol) {
|
|
29752
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, { symbol });
|
|
29753
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29754
|
+
throw new Error("getPositionInvestedCount requires an execution context");
|
|
29755
|
+
}
|
|
29756
|
+
if (!MethodContextService.hasContext()) {
|
|
29757
|
+
throw new Error("getPositionInvestedCount requires a method context");
|
|
29758
|
+
}
|
|
29759
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29760
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29761
|
+
return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29762
|
+
}
|
|
29763
|
+
async function getPositionInvestedCost(symbol) {
|
|
29764
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, { symbol });
|
|
29765
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29766
|
+
throw new Error("getPositionInvestedCost requires an execution context");
|
|
29767
|
+
}
|
|
29768
|
+
if (!MethodContextService.hasContext()) {
|
|
29769
|
+
throw new Error("getPositionInvestedCost requires a method context");
|
|
29770
|
+
}
|
|
29771
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29772
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29773
|
+
return await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29774
|
+
}
|
|
29775
|
+
async function getPositionPnlPercent(symbol) {
|
|
29776
|
+
bt.loggerService.info(GET_POSITION_PNL_PERCENT_METHOD_NAME, { symbol });
|
|
29777
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29778
|
+
throw new Error("getPositionPnlPercent requires an execution context");
|
|
29779
|
+
}
|
|
29780
|
+
if (!MethodContextService.hasContext()) {
|
|
29781
|
+
throw new Error("getPositionPnlPercent requires a method context");
|
|
29782
|
+
}
|
|
29783
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29784
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29785
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29786
|
+
return await bt.strategyCoreService.getPositionPnlPercent(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
29787
|
+
}
|
|
29788
|
+
/**
|
|
29789
|
+
* Executes partial close at profit level by absolute dollar amount (moving toward TP).
|
|
29790
|
+
*
|
|
29791
|
+
* Convenience wrapper around commitPartialProfit that converts a dollar amount
|
|
29792
|
+
* to a percentage of the invested position cost automatically.
|
|
29793
|
+
* Price must be moving toward take profit (in profit direction).
|
|
29794
|
+
*
|
|
29795
|
+
* Automatically detects backtest/live mode from execution context.
|
|
29796
|
+
* Automatically fetches current price via getAveragePrice.
|
|
29797
|
+
*
|
|
29798
|
+
* @param symbol - Trading pair symbol
|
|
29799
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 150 closes $150 worth)
|
|
29800
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
29801
|
+
*
|
|
29802
|
+
* @throws Error if currentPrice is not in profit direction:
|
|
29803
|
+
* - LONG: currentPrice must be > priceOpen
|
|
29804
|
+
* - SHORT: currentPrice must be < priceOpen
|
|
29805
|
+
*
|
|
29806
|
+
* @example
|
|
29807
|
+
* ```typescript
|
|
29808
|
+
* import { commitPartialProfitCost } from "backtest-kit";
|
|
29809
|
+
*
|
|
29810
|
+
* // Close $150 of a $300 position (50%) at profit
|
|
29811
|
+
* const success = await commitPartialProfitCost("BTCUSDT", 150);
|
|
29812
|
+
* if (success) {
|
|
29813
|
+
* console.log('Partial profit executed');
|
|
29814
|
+
* }
|
|
29815
|
+
* ```
|
|
29816
|
+
*/
|
|
29817
|
+
async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
29818
|
+
bt.loggerService.info(PARTIAL_PROFIT_COST_METHOD_NAME, { symbol, dollarAmount });
|
|
29819
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29820
|
+
throw new Error("commitPartialProfitCost requires an execution context");
|
|
29821
|
+
}
|
|
29822
|
+
if (!MethodContextService.hasContext()) {
|
|
29823
|
+
throw new Error("commitPartialProfitCost requires a method context");
|
|
29824
|
+
}
|
|
29825
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29826
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29827
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29828
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29829
|
+
if (investedCost === null)
|
|
29830
|
+
return false;
|
|
29831
|
+
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
29832
|
+
return await bt.strategyCoreService.partialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
29833
|
+
}
|
|
29834
|
+
/**
|
|
29835
|
+
* Executes partial close at loss level by absolute dollar amount (moving toward SL).
|
|
29836
|
+
*
|
|
29837
|
+
* Convenience wrapper around commitPartialLoss that converts a dollar amount
|
|
29838
|
+
* to a percentage of the invested position cost automatically.
|
|
29839
|
+
* Price must be moving toward stop loss (in loss direction).
|
|
29840
|
+
*
|
|
29841
|
+
* Automatically detects backtest/live mode from execution context.
|
|
29842
|
+
* Automatically fetches current price via getAveragePrice.
|
|
29843
|
+
*
|
|
29844
|
+
* @param symbol - Trading pair symbol
|
|
29845
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 100 closes $100 worth)
|
|
29846
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
29847
|
+
*
|
|
29848
|
+
* @throws Error if currentPrice is not in loss direction:
|
|
29849
|
+
* - LONG: currentPrice must be < priceOpen
|
|
29850
|
+
* - SHORT: currentPrice must be > priceOpen
|
|
29851
|
+
*
|
|
29852
|
+
* @example
|
|
29853
|
+
* ```typescript
|
|
29854
|
+
* import { commitPartialLossCost } from "backtest-kit";
|
|
29855
|
+
*
|
|
29856
|
+
* // Close $100 of a $300 position (~33%) at loss
|
|
29857
|
+
* const success = await commitPartialLossCost("BTCUSDT", 100);
|
|
29858
|
+
* if (success) {
|
|
29859
|
+
* console.log('Partial loss executed');
|
|
29860
|
+
* }
|
|
29861
|
+
* ```
|
|
29862
|
+
*/
|
|
29863
|
+
async function commitPartialLossCost(symbol, dollarAmount) {
|
|
29864
|
+
bt.loggerService.info(PARTIAL_LOSS_COST_METHOD_NAME, { symbol, dollarAmount });
|
|
29865
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29866
|
+
throw new Error("commitPartialLossCost requires an execution context");
|
|
29867
|
+
}
|
|
29868
|
+
if (!MethodContextService.hasContext()) {
|
|
29869
|
+
throw new Error("commitPartialLossCost requires a method context");
|
|
29870
|
+
}
|
|
29871
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29872
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29873
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29874
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29875
|
+
if (investedCost === null)
|
|
29876
|
+
return false;
|
|
29877
|
+
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
29878
|
+
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
29879
|
+
}
|
|
29880
|
+
async function getPositionPnlCost(symbol) {
|
|
29881
|
+
bt.loggerService.info(GET_POSITION_PNL_COST_METHOD_NAME, { symbol });
|
|
29882
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29883
|
+
throw new Error("getPositionPnlCost requires an execution context");
|
|
29884
|
+
}
|
|
29885
|
+
if (!MethodContextService.hasContext()) {
|
|
29886
|
+
throw new Error("getPositionPnlCost requires a method context");
|
|
29887
|
+
}
|
|
29888
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
29889
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29890
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29891
|
+
return await bt.strategyCoreService.getPositionPnlCost(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
29892
|
+
}
|
|
29893
|
+
/**
|
|
29894
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
29895
|
+
*
|
|
29896
|
+
* The first element is always the original priceOpen (initial entry).
|
|
29897
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
29898
|
+
*
|
|
29899
|
+
* Returns null if no pending signal exists.
|
|
29900
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
29901
|
+
*
|
|
29902
|
+
* @param symbol - Trading pair symbol
|
|
29903
|
+
* @returns Promise resolving to array of entry prices or null
|
|
29904
|
+
*
|
|
29905
|
+
* @example
|
|
29906
|
+
* ```typescript
|
|
29907
|
+
* import { getPositionLevels } from "backtest-kit";
|
|
29908
|
+
*
|
|
29909
|
+
* const levels = await getPositionLevels("BTCUSDT");
|
|
29910
|
+
* // No DCA: [43000]
|
|
29911
|
+
* // One DCA: [43000, 42000]
|
|
29912
|
+
* ```
|
|
29913
|
+
*/
|
|
29914
|
+
async function getPositionLevels(symbol) {
|
|
29915
|
+
bt.loggerService.info(GET_POSITION_LEVELS_METHOD_NAME, { symbol });
|
|
29916
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29917
|
+
throw new Error("getPositionLevels requires an execution context");
|
|
29918
|
+
}
|
|
29919
|
+
if (!MethodContextService.hasContext()) {
|
|
29920
|
+
throw new Error("getPositionLevels requires a method context");
|
|
29921
|
+
}
|
|
29922
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29923
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29924
|
+
return await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29925
|
+
}
|
|
29926
|
+
async function getPositionPartials(symbol) {
|
|
29927
|
+
bt.loggerService.info(GET_POSITION_PARTIALS_METHOD_NAME, { symbol });
|
|
29928
|
+
if (!ExecutionContextService.hasContext()) {
|
|
29929
|
+
throw new Error("getPositionPartials requires an execution context");
|
|
29930
|
+
}
|
|
29931
|
+
if (!MethodContextService.hasContext()) {
|
|
29932
|
+
throw new Error("getPositionPartials requires a method context");
|
|
29933
|
+
}
|
|
29934
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
29935
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
29936
|
+
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
29937
|
+
}
|
|
29460
29938
|
|
|
29461
29939
|
const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
|
|
29462
29940
|
/**
|
|
@@ -30648,11 +31126,20 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
|
|
|
30648
31126
|
const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
|
|
30649
31127
|
const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
|
|
30650
31128
|
const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
|
|
31129
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionAveragePrice";
|
|
31130
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
|
|
31131
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
|
|
31132
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
|
|
31133
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnlCost";
|
|
31134
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
31135
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
30651
31136
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
30652
31137
|
const BACKTEST_METHOD_NAME_CANCEL_SCHEDULED = "Backtest.commitCancelScheduled";
|
|
30653
31138
|
const BACKTEST_METHOD_NAME_CLOSE_PENDING = "Backtest.commitClosePending";
|
|
30654
31139
|
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.commitPartialProfit";
|
|
30655
31140
|
const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
|
|
31141
|
+
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST = "BacktestUtils.commitPartialProfitCost";
|
|
31142
|
+
const BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST = "BacktestUtils.commitPartialLossCost";
|
|
30656
31143
|
const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
|
|
30657
31144
|
const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
|
|
30658
31145
|
const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
|
|
@@ -31189,6 +31676,134 @@ class BacktestUtils {
|
|
|
31189
31676
|
}
|
|
31190
31677
|
return await bt.strategyCoreService.getBreakeven(true, symbol, currentPrice, context);
|
|
31191
31678
|
};
|
|
31679
|
+
this.getPositionAveragePrice = async (symbol, context) => {
|
|
31680
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
31681
|
+
symbol,
|
|
31682
|
+
context,
|
|
31683
|
+
});
|
|
31684
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
31685
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
31686
|
+
{
|
|
31687
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31688
|
+
riskName &&
|
|
31689
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
31690
|
+
riskList &&
|
|
31691
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
31692
|
+
actions &&
|
|
31693
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
31694
|
+
}
|
|
31695
|
+
return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
31696
|
+
};
|
|
31697
|
+
this.getPositionInvestedCount = async (symbol, context) => {
|
|
31698
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
31699
|
+
symbol,
|
|
31700
|
+
context,
|
|
31701
|
+
});
|
|
31702
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
31703
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
31704
|
+
{
|
|
31705
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31706
|
+
riskName &&
|
|
31707
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
31708
|
+
riskList &&
|
|
31709
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
31710
|
+
actions &&
|
|
31711
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
31712
|
+
}
|
|
31713
|
+
return await bt.strategyCoreService.getPositionInvestedCount(true, symbol, context);
|
|
31714
|
+
};
|
|
31715
|
+
this.getPositionInvestedCost = async (symbol, context) => {
|
|
31716
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
31717
|
+
symbol,
|
|
31718
|
+
context,
|
|
31719
|
+
});
|
|
31720
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
31721
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
31722
|
+
{
|
|
31723
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31724
|
+
riskName &&
|
|
31725
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
31726
|
+
riskList &&
|
|
31727
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
31728
|
+
actions &&
|
|
31729
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
31730
|
+
}
|
|
31731
|
+
return await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
31732
|
+
};
|
|
31733
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
31734
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
31735
|
+
symbol,
|
|
31736
|
+
currentPrice,
|
|
31737
|
+
context,
|
|
31738
|
+
});
|
|
31739
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
31740
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
31741
|
+
{
|
|
31742
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31743
|
+
riskName &&
|
|
31744
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
31745
|
+
riskList &&
|
|
31746
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
31747
|
+
actions &&
|
|
31748
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
31749
|
+
}
|
|
31750
|
+
return await bt.strategyCoreService.getPositionPnlPercent(true, symbol, currentPrice, context);
|
|
31751
|
+
};
|
|
31752
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
31753
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
31754
|
+
symbol,
|
|
31755
|
+
currentPrice,
|
|
31756
|
+
context,
|
|
31757
|
+
});
|
|
31758
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
31759
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
31760
|
+
{
|
|
31761
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31762
|
+
riskName &&
|
|
31763
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
31764
|
+
riskList &&
|
|
31765
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
31766
|
+
actions &&
|
|
31767
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
31768
|
+
}
|
|
31769
|
+
return await bt.strategyCoreService.getPositionPnlCost(true, symbol, currentPrice, context);
|
|
31770
|
+
};
|
|
31771
|
+
this.getPositionLevels = async (symbol, context) => {
|
|
31772
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
31773
|
+
symbol,
|
|
31774
|
+
context,
|
|
31775
|
+
});
|
|
31776
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS);
|
|
31777
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS);
|
|
31778
|
+
{
|
|
31779
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31780
|
+
riskName &&
|
|
31781
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS);
|
|
31782
|
+
riskList &&
|
|
31783
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS));
|
|
31784
|
+
actions &&
|
|
31785
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_LEVELS));
|
|
31786
|
+
}
|
|
31787
|
+
return await bt.strategyCoreService.getPositionLevels(true, symbol, context);
|
|
31788
|
+
};
|
|
31789
|
+
this.getPositionPartials = async (symbol, context) => {
|
|
31790
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
31791
|
+
symbol,
|
|
31792
|
+
context,
|
|
31793
|
+
});
|
|
31794
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
31795
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
31796
|
+
{
|
|
31797
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
31798
|
+
riskName &&
|
|
31799
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
31800
|
+
riskList &&
|
|
31801
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
31802
|
+
actions &&
|
|
31803
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
31804
|
+
}
|
|
31805
|
+
return await bt.strategyCoreService.getPositionPartials(true, symbol, context);
|
|
31806
|
+
};
|
|
31192
31807
|
/**
|
|
31193
31808
|
* Stops the strategy from generating new signals.
|
|
31194
31809
|
*
|
|
@@ -31410,6 +32025,114 @@ class BacktestUtils {
|
|
|
31410
32025
|
}
|
|
31411
32026
|
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
31412
32027
|
};
|
|
32028
|
+
/**
|
|
32029
|
+
* Executes partial close at profit level by absolute dollar amount (moving toward TP).
|
|
32030
|
+
*
|
|
32031
|
+
* Convenience wrapper around commitPartialProfit that converts a dollar amount
|
|
32032
|
+
* to a percentage of the invested position cost automatically.
|
|
32033
|
+
* Price must be moving toward take profit (in profit direction).
|
|
32034
|
+
*
|
|
32035
|
+
* @param symbol - Trading pair symbol
|
|
32036
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 150 closes $150 worth)
|
|
32037
|
+
* @param currentPrice - Current market price for this partial close
|
|
32038
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
32039
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
32040
|
+
*
|
|
32041
|
+
* @throws Error if currentPrice is not in profit direction:
|
|
32042
|
+
* - LONG: currentPrice must be > priceOpen
|
|
32043
|
+
* - SHORT: currentPrice must be < priceOpen
|
|
32044
|
+
*
|
|
32045
|
+
* @example
|
|
32046
|
+
* ```typescript
|
|
32047
|
+
* // Close $150 of a $300 position (50%) at profit
|
|
32048
|
+
* const success = await Backtest.commitPartialProfitCost("BTCUSDT", 150, 45000, {
|
|
32049
|
+
* exchangeName: "binance",
|
|
32050
|
+
* frameName: "frame1",
|
|
32051
|
+
* strategyName: "my-strategy"
|
|
32052
|
+
* });
|
|
32053
|
+
* if (success) {
|
|
32054
|
+
* console.log('Partial profit executed');
|
|
32055
|
+
* }
|
|
32056
|
+
* ```
|
|
32057
|
+
*/
|
|
32058
|
+
this.commitPartialProfitCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
32059
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST, {
|
|
32060
|
+
symbol,
|
|
32061
|
+
dollarAmount,
|
|
32062
|
+
currentPrice,
|
|
32063
|
+
context,
|
|
32064
|
+
});
|
|
32065
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
32066
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
32067
|
+
{
|
|
32068
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
32069
|
+
riskName &&
|
|
32070
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
32071
|
+
riskList &&
|
|
32072
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
32073
|
+
actions &&
|
|
32074
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
32075
|
+
}
|
|
32076
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
32077
|
+
if (investedCost === null)
|
|
32078
|
+
return false;
|
|
32079
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
32080
|
+
return await bt.strategyCoreService.partialProfit(true, symbol, percentToClose, currentPrice, context);
|
|
32081
|
+
};
|
|
32082
|
+
/**
|
|
32083
|
+
* Executes partial close at loss level by absolute dollar amount (moving toward SL).
|
|
32084
|
+
*
|
|
32085
|
+
* Convenience wrapper around commitPartialLoss that converts a dollar amount
|
|
32086
|
+
* to a percentage of the invested position cost automatically.
|
|
32087
|
+
* Price must be moving toward stop loss (in loss direction).
|
|
32088
|
+
*
|
|
32089
|
+
* @param symbol - Trading pair symbol
|
|
32090
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 100 closes $100 worth)
|
|
32091
|
+
* @param currentPrice - Current market price for this partial close
|
|
32092
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
32093
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
32094
|
+
*
|
|
32095
|
+
* @throws Error if currentPrice is not in loss direction:
|
|
32096
|
+
* - LONG: currentPrice must be < priceOpen
|
|
32097
|
+
* - SHORT: currentPrice must be > priceOpen
|
|
32098
|
+
*
|
|
32099
|
+
* @example
|
|
32100
|
+
* ```typescript
|
|
32101
|
+
* // Close $100 of a $300 position (~33%) at loss
|
|
32102
|
+
* const success = await Backtest.commitPartialLossCost("BTCUSDT", 100, 38000, {
|
|
32103
|
+
* exchangeName: "binance",
|
|
32104
|
+
* frameName: "frame1",
|
|
32105
|
+
* strategyName: "my-strategy"
|
|
32106
|
+
* });
|
|
32107
|
+
* if (success) {
|
|
32108
|
+
* console.log('Partial loss executed');
|
|
32109
|
+
* }
|
|
32110
|
+
* ```
|
|
32111
|
+
*/
|
|
32112
|
+
this.commitPartialLossCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
32113
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST, {
|
|
32114
|
+
symbol,
|
|
32115
|
+
dollarAmount,
|
|
32116
|
+
currentPrice,
|
|
32117
|
+
context,
|
|
32118
|
+
});
|
|
32119
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
32120
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
32121
|
+
{
|
|
32122
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
32123
|
+
riskName &&
|
|
32124
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
32125
|
+
riskList &&
|
|
32126
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
32127
|
+
actions &&
|
|
32128
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
32129
|
+
}
|
|
32130
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
32131
|
+
if (investedCost === null)
|
|
32132
|
+
return false;
|
|
32133
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
32134
|
+
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
32135
|
+
};
|
|
31413
32136
|
/**
|
|
31414
32137
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
31415
32138
|
*
|
|
@@ -31831,11 +32554,20 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
|
|
|
31831
32554
|
const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
|
|
31832
32555
|
const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
|
|
31833
32556
|
const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
|
|
32557
|
+
const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionAveragePrice";
|
|
32558
|
+
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
|
|
32559
|
+
const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
|
|
32560
|
+
const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
|
|
32561
|
+
const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
32562
|
+
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
32563
|
+
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
31834
32564
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
31835
32565
|
const LIVE_METHOD_NAME_CANCEL_SCHEDULED = "Live.cancelScheduled";
|
|
31836
32566
|
const LIVE_METHOD_NAME_CLOSE_PENDING = "Live.closePending";
|
|
31837
32567
|
const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.commitPartialProfit";
|
|
31838
32568
|
const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
|
|
32569
|
+
const LIVE_METHOD_NAME_PARTIAL_PROFIT_COST = "LiveUtils.commitPartialProfitCost";
|
|
32570
|
+
const LIVE_METHOD_NAME_PARTIAL_LOSS_COST = "LiveUtils.commitPartialLossCost";
|
|
31839
32571
|
const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
|
|
31840
32572
|
const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
|
|
31841
32573
|
const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
|
|
@@ -32344,6 +33076,118 @@ class LiveUtils {
|
|
|
32344
33076
|
frameName: "",
|
|
32345
33077
|
});
|
|
32346
33078
|
};
|
|
33079
|
+
this.getPositionAveragePrice = async (symbol, context) => {
|
|
33080
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, { symbol, context });
|
|
33081
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
33082
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
33083
|
+
{
|
|
33084
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33085
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
33086
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
33087
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
33088
|
+
}
|
|
33089
|
+
return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
33090
|
+
strategyName: context.strategyName,
|
|
33091
|
+
exchangeName: context.exchangeName,
|
|
33092
|
+
frameName: "",
|
|
33093
|
+
});
|
|
33094
|
+
};
|
|
33095
|
+
this.getPositionInvestedCount = async (symbol, context) => {
|
|
33096
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT, { symbol, context });
|
|
33097
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
33098
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
33099
|
+
{
|
|
33100
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33101
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
33102
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
33103
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
33104
|
+
}
|
|
33105
|
+
return await bt.strategyCoreService.getPositionInvestedCount(false, symbol, {
|
|
33106
|
+
strategyName: context.strategyName,
|
|
33107
|
+
exchangeName: context.exchangeName,
|
|
33108
|
+
frameName: "",
|
|
33109
|
+
});
|
|
33110
|
+
};
|
|
33111
|
+
this.getPositionInvestedCost = async (symbol, context) => {
|
|
33112
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST, { symbol, context });
|
|
33113
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
33114
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
33115
|
+
{
|
|
33116
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33117
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
33118
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
33119
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
33120
|
+
}
|
|
33121
|
+
return await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
33122
|
+
strategyName: context.strategyName,
|
|
33123
|
+
exchangeName: context.exchangeName,
|
|
33124
|
+
frameName: "",
|
|
33125
|
+
});
|
|
33126
|
+
};
|
|
33127
|
+
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
33128
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
|
|
33129
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
33130
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
33131
|
+
{
|
|
33132
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33133
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
33134
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
33135
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
33136
|
+
}
|
|
33137
|
+
return await bt.strategyCoreService.getPositionPnlPercent(false, symbol, currentPrice, {
|
|
33138
|
+
strategyName: context.strategyName,
|
|
33139
|
+
exchangeName: context.exchangeName,
|
|
33140
|
+
frameName: "",
|
|
33141
|
+
});
|
|
33142
|
+
};
|
|
33143
|
+
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
33144
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
|
|
33145
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
33146
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
33147
|
+
{
|
|
33148
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33149
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
33150
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
33151
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
33152
|
+
}
|
|
33153
|
+
return await bt.strategyCoreService.getPositionPnlCost(false, symbol, currentPrice, {
|
|
33154
|
+
strategyName: context.strategyName,
|
|
33155
|
+
exchangeName: context.exchangeName,
|
|
33156
|
+
frameName: "",
|
|
33157
|
+
});
|
|
33158
|
+
};
|
|
33159
|
+
this.getPositionLevels = async (symbol, context) => {
|
|
33160
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_LEVELS, { symbol, context });
|
|
33161
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
33162
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
33163
|
+
{
|
|
33164
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33165
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
33166
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
33167
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
33168
|
+
}
|
|
33169
|
+
return await bt.strategyCoreService.getPositionLevels(false, symbol, {
|
|
33170
|
+
strategyName: context.strategyName,
|
|
33171
|
+
exchangeName: context.exchangeName,
|
|
33172
|
+
frameName: "",
|
|
33173
|
+
});
|
|
33174
|
+
};
|
|
33175
|
+
this.getPositionPartials = async (symbol, context) => {
|
|
33176
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIALS, { symbol, context });
|
|
33177
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
33178
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
33179
|
+
{
|
|
33180
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33181
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
33182
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
33183
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
33184
|
+
}
|
|
33185
|
+
return await bt.strategyCoreService.getPositionPartials(false, symbol, {
|
|
33186
|
+
strategyName: context.strategyName,
|
|
33187
|
+
exchangeName: context.exchangeName,
|
|
33188
|
+
frameName: "",
|
|
33189
|
+
});
|
|
33190
|
+
};
|
|
32347
33191
|
/**
|
|
32348
33192
|
* Stops the strategy from generating new signals.
|
|
32349
33193
|
*
|
|
@@ -32562,6 +33406,112 @@ class LiveUtils {
|
|
|
32562
33406
|
frameName: "",
|
|
32563
33407
|
});
|
|
32564
33408
|
};
|
|
33409
|
+
/**
|
|
33410
|
+
* Executes partial close at profit level by absolute dollar amount (moving toward TP).
|
|
33411
|
+
*
|
|
33412
|
+
* Convenience wrapper around commitPartialProfit that converts a dollar amount
|
|
33413
|
+
* to a percentage of the invested position cost automatically.
|
|
33414
|
+
* Price must be moving toward take profit (in profit direction).
|
|
33415
|
+
*
|
|
33416
|
+
* @param symbol - Trading pair symbol
|
|
33417
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 150 closes $150 worth)
|
|
33418
|
+
* @param currentPrice - Current market price for this partial close
|
|
33419
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
33420
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
33421
|
+
*
|
|
33422
|
+
* @throws Error if currentPrice is not in profit direction:
|
|
33423
|
+
* - LONG: currentPrice must be > priceOpen
|
|
33424
|
+
* - SHORT: currentPrice must be < priceOpen
|
|
33425
|
+
*
|
|
33426
|
+
* @example
|
|
33427
|
+
* ```typescript
|
|
33428
|
+
* // Close $150 of a $300 position (50%) at profit
|
|
33429
|
+
* const success = await Live.commitPartialProfitCost("BTCUSDT", 150, 45000, {
|
|
33430
|
+
* exchangeName: "binance",
|
|
33431
|
+
* strategyName: "my-strategy"
|
|
33432
|
+
* });
|
|
33433
|
+
* if (success) {
|
|
33434
|
+
* console.log('Partial profit executed');
|
|
33435
|
+
* }
|
|
33436
|
+
* ```
|
|
33437
|
+
*/
|
|
33438
|
+
this.commitPartialProfitCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
33439
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_PROFIT_COST, { symbol, dollarAmount, currentPrice, context });
|
|
33440
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
33441
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
33442
|
+
{
|
|
33443
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33444
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
33445
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
33446
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
33447
|
+
}
|
|
33448
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
33449
|
+
strategyName: context.strategyName,
|
|
33450
|
+
exchangeName: context.exchangeName,
|
|
33451
|
+
frameName: "",
|
|
33452
|
+
});
|
|
33453
|
+
if (investedCost === null)
|
|
33454
|
+
return false;
|
|
33455
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
33456
|
+
return await bt.strategyCoreService.partialProfit(false, symbol, percentToClose, currentPrice, {
|
|
33457
|
+
strategyName: context.strategyName,
|
|
33458
|
+
exchangeName: context.exchangeName,
|
|
33459
|
+
frameName: "",
|
|
33460
|
+
});
|
|
33461
|
+
};
|
|
33462
|
+
/**
|
|
33463
|
+
* Executes partial close at loss level by absolute dollar amount (moving toward SL).
|
|
33464
|
+
*
|
|
33465
|
+
* Convenience wrapper around commitPartialLoss that converts a dollar amount
|
|
33466
|
+
* to a percentage of the invested position cost automatically.
|
|
33467
|
+
* Price must be moving toward stop loss (in loss direction).
|
|
33468
|
+
*
|
|
33469
|
+
* @param symbol - Trading pair symbol
|
|
33470
|
+
* @param dollarAmount - Dollar value of position to close (e.g. 100 closes $100 worth)
|
|
33471
|
+
* @param currentPrice - Current market price for this partial close
|
|
33472
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
33473
|
+
* @returns Promise<boolean> - true if partial close executed, false if skipped or no position
|
|
33474
|
+
*
|
|
33475
|
+
* @throws Error if currentPrice is not in loss direction:
|
|
33476
|
+
* - LONG: currentPrice must be < priceOpen
|
|
33477
|
+
* - SHORT: currentPrice must be > priceOpen
|
|
33478
|
+
*
|
|
33479
|
+
* @example
|
|
33480
|
+
* ```typescript
|
|
33481
|
+
* // Close $100 of a $300 position (~33%) at loss
|
|
33482
|
+
* const success = await Live.commitPartialLossCost("BTCUSDT", 100, 38000, {
|
|
33483
|
+
* exchangeName: "binance",
|
|
33484
|
+
* strategyName: "my-strategy"
|
|
33485
|
+
* });
|
|
33486
|
+
* if (success) {
|
|
33487
|
+
* console.log('Partial loss executed');
|
|
33488
|
+
* }
|
|
33489
|
+
* ```
|
|
33490
|
+
*/
|
|
33491
|
+
this.commitPartialLossCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
33492
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_LOSS_COST, { symbol, dollarAmount, currentPrice, context });
|
|
33493
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
33494
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
33495
|
+
{
|
|
33496
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33497
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
33498
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
33499
|
+
actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
33500
|
+
}
|
|
33501
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
33502
|
+
strategyName: context.strategyName,
|
|
33503
|
+
exchangeName: context.exchangeName,
|
|
33504
|
+
frameName: "",
|
|
33505
|
+
});
|
|
33506
|
+
if (investedCost === null)
|
|
33507
|
+
return false;
|
|
33508
|
+
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
33509
|
+
return await bt.strategyCoreService.partialLoss(false, symbol, percentToClose, currentPrice, {
|
|
33510
|
+
strategyName: context.strategyName,
|
|
33511
|
+
exchangeName: context.exchangeName,
|
|
33512
|
+
frameName: "",
|
|
33513
|
+
});
|
|
33514
|
+
};
|
|
32565
33515
|
/**
|
|
32566
33516
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
32567
33517
|
*
|
|
@@ -40082,4 +41032,25 @@ const set = (object, path, value) => {
|
|
|
40082
41032
|
}
|
|
40083
41033
|
};
|
|
40084
41034
|
|
|
40085
|
-
|
|
41035
|
+
/**
|
|
41036
|
+
* Calculate the percentage difference between two numbers.
|
|
41037
|
+
* @param {number} a - The first number.
|
|
41038
|
+
* @param {number} b - The second number.
|
|
41039
|
+
* @returns {number} The percentage difference between the two numbers.
|
|
41040
|
+
*/
|
|
41041
|
+
const percentDiff = (a = 1, b = 2) => {
|
|
41042
|
+
const result = 100 / (Math.min(a, b) / Math.max(a, b)) - 100;
|
|
41043
|
+
return Number.isFinite(result) ? result : 100;
|
|
41044
|
+
};
|
|
41045
|
+
|
|
41046
|
+
/**
|
|
41047
|
+
* Calculate the percentage change from yesterday's value to today's value.
|
|
41048
|
+
* @param {number} yesterdayValue - The value from yesterday.
|
|
41049
|
+
* @param {number} todayValue - The value from today.
|
|
41050
|
+
* @returns {number} The percentage change from yesterday to today.
|
|
41051
|
+
*/
|
|
41052
|
+
const percentValue = (yesterdayValue, todayValue) => {
|
|
41053
|
+
return yesterdayValue / todayValue - 1;
|
|
41054
|
+
};
|
|
41055
|
+
|
|
41056
|
+
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, stopStrategy, toProfitLossDto, validate, waitForCandle, warmCandles };
|