backtest-kit 4.0.1 → 5.0.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 +575 -25
- package/build/index.mjs +574 -26
- package/package.json +1 -1
- package/types.d.ts +358 -16
package/build/index.mjs
CHANGED
|
@@ -450,9 +450,17 @@ const GLOBAL_CONFIG = {
|
|
|
450
450
|
* Allows to commitAverageBuy if currentPrice is not the lowest price since entry, but still lower than priceOpen.
|
|
451
451
|
* This can help improve average entry price in cases where price has rebounded after entry but is still below priceOpen, without waiting for a new lower price.
|
|
452
452
|
*
|
|
453
|
-
* Default:
|
|
453
|
+
* Default: false (DCA logic enabled only when antirecord is broken)
|
|
454
454
|
*/
|
|
455
455
|
CC_ENABLE_DCA_EVERYWHERE: false,
|
|
456
|
+
/**
|
|
457
|
+
* Enables PPPL (Partial Profit, Partial Loss) logic even if this breaks a direction of exits
|
|
458
|
+
* Allows to take partial profit or loss on a position even if it results in a mix of profit and loss exits
|
|
459
|
+
* This can help lock in profits or cut losses on part of the position without waiting for a perfect exit scenario.
|
|
460
|
+
*
|
|
461
|
+
* Default: false (PPPL logic is only applied when it does not break the direction of exits, ensuring clearer profit/loss outcomes)
|
|
462
|
+
*/
|
|
463
|
+
CC_ENABLE_PPPL_EVERYWHERE: false,
|
|
456
464
|
/**
|
|
457
465
|
* Cost of entering a position (in USD).
|
|
458
466
|
* This is used as a default value for calculating position size and risk management when cost data is not provided by the strategy
|
|
@@ -6610,6 +6618,18 @@ class ClientStrategy {
|
|
|
6610
6618
|
}
|
|
6611
6619
|
return entries.map((e) => e.price);
|
|
6612
6620
|
}
|
|
6621
|
+
/**
|
|
6622
|
+
* Returns the list of partial closes for the current pending signal.
|
|
6623
|
+
*
|
|
6624
|
+
* Each entry records a partial profit or loss close event with its type,
|
|
6625
|
+
* percent closed, price at close, cost basis snapshot, and entry count at close.
|
|
6626
|
+
*
|
|
6627
|
+
* Returns null if no pending signal exists.
|
|
6628
|
+
* Returns an empty array if no partial closes have been executed.
|
|
6629
|
+
*
|
|
6630
|
+
* @param symbol - Trading pair symbol
|
|
6631
|
+
* @returns Promise resolving to array of partial close records or null
|
|
6632
|
+
*/
|
|
6613
6633
|
async getPositionPartials(symbol) {
|
|
6614
6634
|
this.params.logger.debug("ClientStrategy getPositionPartials", { symbol });
|
|
6615
6635
|
if (!this._pendingSignal) {
|
|
@@ -6617,6 +6637,34 @@ class ClientStrategy {
|
|
|
6617
6637
|
}
|
|
6618
6638
|
return this._pendingSignal._partial ?? [];
|
|
6619
6639
|
}
|
|
6640
|
+
/**
|
|
6641
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
6642
|
+
*
|
|
6643
|
+
* Each entry records the price and cost of a single position entry.
|
|
6644
|
+
* The first element is always the original priceOpen (initial entry).
|
|
6645
|
+
* Each subsequent element is an entry added by averageBuy().
|
|
6646
|
+
*
|
|
6647
|
+
* Returns null if no pending signal exists.
|
|
6648
|
+
* Returns a single-element array [{ price: priceOpen, cost }] if no DCA entries were made.
|
|
6649
|
+
*
|
|
6650
|
+
* @param symbol - Trading pair symbol
|
|
6651
|
+
* @returns Promise resolving to array of entry records or null
|
|
6652
|
+
*
|
|
6653
|
+
* @example
|
|
6654
|
+
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
6655
|
+
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
6656
|
+
*/
|
|
6657
|
+
async getPositionEntries(symbol) {
|
|
6658
|
+
this.params.logger.debug("ClientStrategy getPositionEntries", { symbol });
|
|
6659
|
+
if (!this._pendingSignal) {
|
|
6660
|
+
return null;
|
|
6661
|
+
}
|
|
6662
|
+
const entries = this._pendingSignal._entry;
|
|
6663
|
+
if (!entries || entries.length === 0) {
|
|
6664
|
+
return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST }];
|
|
6665
|
+
}
|
|
6666
|
+
return entries.map(({ price, cost }) => ({ price, cost }));
|
|
6667
|
+
}
|
|
6620
6668
|
/**
|
|
6621
6669
|
* Performs a single tick of strategy execution.
|
|
6622
6670
|
*
|
|
@@ -7388,10 +7436,12 @@ class ClientStrategy {
|
|
|
7388
7436
|
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7389
7437
|
return false;
|
|
7390
7438
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7391
|
-
if (
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7439
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7440
|
+
if (this._pendingSignal.position === "long" && currentPrice <= effectivePriceOpen)
|
|
7441
|
+
return false;
|
|
7442
|
+
if (this._pendingSignal.position === "short" && currentPrice >= effectivePriceOpen)
|
|
7443
|
+
return false;
|
|
7444
|
+
}
|
|
7395
7445
|
const effectiveTakeProfit = this._pendingSignal._trailingPriceTakeProfit ?? this._pendingSignal.priceTakeProfit;
|
|
7396
7446
|
if (this._pendingSignal.position === "long" && currentPrice >= effectiveTakeProfit)
|
|
7397
7447
|
return false;
|
|
@@ -7475,7 +7525,7 @@ class ClientStrategy {
|
|
|
7475
7525
|
throw new Error(`ClientStrategy partialProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
7476
7526
|
}
|
|
7477
7527
|
// Validation: currentPrice must be moving toward TP (profit direction)
|
|
7478
|
-
{
|
|
7528
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7479
7529
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7480
7530
|
if (this._pendingSignal.position === "long") {
|
|
7481
7531
|
// For LONG: currentPrice must be higher than effectivePriceOpen (moving toward TP)
|
|
@@ -7569,10 +7619,12 @@ class ClientStrategy {
|
|
|
7569
7619
|
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7570
7620
|
return false;
|
|
7571
7621
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7572
|
-
if (
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7622
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7623
|
+
if (this._pendingSignal.position === "long" && currentPrice >= effectivePriceOpen)
|
|
7624
|
+
return false;
|
|
7625
|
+
if (this._pendingSignal.position === "short" && currentPrice <= effectivePriceOpen)
|
|
7626
|
+
return false;
|
|
7627
|
+
}
|
|
7576
7628
|
const effectiveStopLoss = this._pendingSignal._trailingPriceStopLoss ?? this._pendingSignal.priceStopLoss;
|
|
7577
7629
|
if (this._pendingSignal.position === "long" && currentPrice <= effectiveStopLoss)
|
|
7578
7630
|
return false;
|
|
@@ -7656,7 +7708,7 @@ class ClientStrategy {
|
|
|
7656
7708
|
throw new Error(`ClientStrategy partialLoss: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
7657
7709
|
}
|
|
7658
7710
|
// Validation: currentPrice must be moving toward SL (loss direction)
|
|
7659
|
-
{
|
|
7711
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7660
7712
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7661
7713
|
if (this._pendingSignal.position === "long") {
|
|
7662
7714
|
// For LONG: currentPrice must be lower than effectivePriceOpen (moving toward SL)
|
|
@@ -9271,6 +9323,15 @@ class StrategyConnectionService {
|
|
|
9271
9323
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9272
9324
|
return await strategy.getPositionPartials(symbol);
|
|
9273
9325
|
};
|
|
9326
|
+
this.getPositionEntries = async (backtest, symbol, context) => {
|
|
9327
|
+
this.loggerService.log("strategyConnectionService getPositionEntries", {
|
|
9328
|
+
symbol,
|
|
9329
|
+
context,
|
|
9330
|
+
backtest,
|
|
9331
|
+
});
|
|
9332
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9333
|
+
return await strategy.getPositionEntries(symbol);
|
|
9334
|
+
};
|
|
9274
9335
|
/**
|
|
9275
9336
|
* Retrieves the currently active scheduled signal for the strategy.
|
|
9276
9337
|
* If no scheduled signal exists, returns null.
|
|
@@ -10795,7 +10856,6 @@ const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
|
|
|
10795
10856
|
const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
|
|
10796
10857
|
const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
|
|
10797
10858
|
const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
|
|
10798
|
-
const METHOD_NAME_SIGNAL_SYNC = "ActionBase.signalSync";
|
|
10799
10859
|
const METHOD_NAME_DISPOSE = "ActionBase.dispose";
|
|
10800
10860
|
const DEFAULT_SOURCE = "default";
|
|
10801
10861
|
/**
|
|
@@ -11207,6 +11267,11 @@ class ActionProxy {
|
|
|
11207
11267
|
*/
|
|
11208
11268
|
async signalSync(event) {
|
|
11209
11269
|
if (this._target.signalSync) {
|
|
11270
|
+
console.error("Action::signalSync is unwanted cause exchange integration should be implemented in Broker.useBrokerAdapter as an infrastructure domain layer");
|
|
11271
|
+
console.error("If you need to implement custom logic on signal open/close, please use signal(), signalBacktest(), signalLive()");
|
|
11272
|
+
console.error("If Action::signalSync throws the exchange will not execute the order!");
|
|
11273
|
+
console.error("");
|
|
11274
|
+
console.error("You have been warned!");
|
|
11210
11275
|
await this._target.signalSync(event);
|
|
11211
11276
|
}
|
|
11212
11277
|
}
|
|
@@ -11669,19 +11734,6 @@ class ActionBase {
|
|
|
11669
11734
|
source,
|
|
11670
11735
|
});
|
|
11671
11736
|
}
|
|
11672
|
-
/**
|
|
11673
|
-
* Gate for position open/close via limit order. Default allows all.
|
|
11674
|
-
* Throw to reject — framework retries next tick.
|
|
11675
|
-
*
|
|
11676
|
-
* NOTE: Exceptions are NOT swallowed — they propagate to CREATE_SYNC_FN.
|
|
11677
|
-
*
|
|
11678
|
-
* @param event - Sync event with action "signal-open" or "signal-close"
|
|
11679
|
-
*/
|
|
11680
|
-
signalSync(_event, source = DEFAULT_SOURCE) {
|
|
11681
|
-
bt.loggerService.info(METHOD_NAME_SIGNAL_SYNC, {
|
|
11682
|
-
source,
|
|
11683
|
-
});
|
|
11684
|
-
}
|
|
11685
11737
|
/**
|
|
11686
11738
|
* Cleans up resources and subscriptions when action handler is disposed.
|
|
11687
11739
|
*
|
|
@@ -12960,6 +13012,14 @@ class StrategyCoreService {
|
|
|
12960
13012
|
await this.validate(context);
|
|
12961
13013
|
return await this.strategyConnectionService.getPositionPartials(backtest, symbol, context);
|
|
12962
13014
|
};
|
|
13015
|
+
this.getPositionEntries = async (backtest, symbol, context) => {
|
|
13016
|
+
this.loggerService.log("strategyCoreService getPositionEntries", {
|
|
13017
|
+
symbol,
|
|
13018
|
+
context,
|
|
13019
|
+
});
|
|
13020
|
+
await this.validate(context);
|
|
13021
|
+
return await this.strategyConnectionService.getPositionEntries(backtest, symbol, context);
|
|
13022
|
+
};
|
|
12963
13023
|
/**
|
|
12964
13024
|
* Retrieves the currently active scheduled signal for the symbol.
|
|
12965
13025
|
* If no scheduled signal exists, returns null.
|
|
@@ -31704,6 +31764,11 @@ BrokerBase = makeExtendable(BrokerBase);
|
|
|
31704
31764
|
*/
|
|
31705
31765
|
const Broker = new BrokerAdapter();
|
|
31706
31766
|
|
|
31767
|
+
const POSITION_OVERLAP_LADDER_DEFAULT = {
|
|
31768
|
+
upperPercent: 1.5,
|
|
31769
|
+
lowerPercent: 1.5,
|
|
31770
|
+
};
|
|
31771
|
+
|
|
31707
31772
|
const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
|
|
31708
31773
|
const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
|
|
31709
31774
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
@@ -31729,6 +31794,8 @@ const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
|
31729
31794
|
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
31730
31795
|
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
31731
31796
|
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
31797
|
+
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
31798
|
+
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
31732
31799
|
/**
|
|
31733
31800
|
* Cancels the scheduled signal without stopping the strategy.
|
|
31734
31801
|
*
|
|
@@ -32005,6 +32072,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
32005
32072
|
percentShift,
|
|
32006
32073
|
currentPrice,
|
|
32007
32074
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
32075
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32008
32076
|
position: signal.position,
|
|
32009
32077
|
context: { exchangeName, frameName, strategyName },
|
|
32010
32078
|
backtest: isBacktest,
|
|
@@ -32084,6 +32152,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
32084
32152
|
percentShift,
|
|
32085
32153
|
currentPrice,
|
|
32086
32154
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
32155
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32087
32156
|
position: signal.position,
|
|
32088
32157
|
context: { exchangeName, frameName, strategyName },
|
|
32089
32158
|
backtest: isBacktest,
|
|
@@ -32135,6 +32204,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
|
32135
32204
|
currentPrice,
|
|
32136
32205
|
newStopLossPrice,
|
|
32137
32206
|
position: signal.position,
|
|
32207
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32138
32208
|
context: { exchangeName, frameName, strategyName },
|
|
32139
32209
|
backtest: isBacktest,
|
|
32140
32210
|
});
|
|
@@ -32184,6 +32254,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
|
32184
32254
|
percentShift,
|
|
32185
32255
|
currentPrice,
|
|
32186
32256
|
newTakeProfitPrice,
|
|
32257
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32187
32258
|
position: signal.position,
|
|
32188
32259
|
context: { exchangeName, frameName, strategyName },
|
|
32189
32260
|
backtest: isBacktest,
|
|
@@ -32510,6 +32581,31 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
32510
32581
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32511
32582
|
return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32512
32583
|
}
|
|
32584
|
+
/**
|
|
32585
|
+
* Returns the effective (DCA-weighted) entry price for the current pending signal.
|
|
32586
|
+
*
|
|
32587
|
+
* Uses cost-weighted harmonic mean: Σcost / Σ(cost/price).
|
|
32588
|
+
* When partial closes exist, the price is computed iteratively using
|
|
32589
|
+
* costBasisAtClose snapshots from each partial, then blended with any
|
|
32590
|
+
* DCA entries added after the last partial.
|
|
32591
|
+
* With no DCA entries, equals the original priceOpen.
|
|
32592
|
+
*
|
|
32593
|
+
* Returns null if no pending signal exists.
|
|
32594
|
+
*
|
|
32595
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32596
|
+
*
|
|
32597
|
+
* @param symbol - Trading pair symbol
|
|
32598
|
+
* @returns Promise resolving to effective entry price or null
|
|
32599
|
+
*
|
|
32600
|
+
* @example
|
|
32601
|
+
* ```typescript
|
|
32602
|
+
* import { getPositionAveragePrice } from "backtest-kit";
|
|
32603
|
+
*
|
|
32604
|
+
* const avgPrice = await getPositionAveragePrice("BTCUSDT");
|
|
32605
|
+
* // No DCA: avgPrice === priceOpen
|
|
32606
|
+
* // After DCA at lower price: avgPrice < priceOpen
|
|
32607
|
+
* ```
|
|
32608
|
+
*/
|
|
32513
32609
|
async function getPositionAveragePrice(symbol) {
|
|
32514
32610
|
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
32515
32611
|
symbol,
|
|
@@ -32524,6 +32620,28 @@ async function getPositionAveragePrice(symbol) {
|
|
|
32524
32620
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32525
32621
|
return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32526
32622
|
}
|
|
32623
|
+
/**
|
|
32624
|
+
* Returns the number of DCA entries made for the current pending signal.
|
|
32625
|
+
*
|
|
32626
|
+
* 1 = original entry only (no DCA).
|
|
32627
|
+
* Increases by 1 with each successful commitAverageBuy().
|
|
32628
|
+
*
|
|
32629
|
+
* Returns null if no pending signal exists.
|
|
32630
|
+
*
|
|
32631
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32632
|
+
*
|
|
32633
|
+
* @param symbol - Trading pair symbol
|
|
32634
|
+
* @returns Promise resolving to entry count or null
|
|
32635
|
+
*
|
|
32636
|
+
* @example
|
|
32637
|
+
* ```typescript
|
|
32638
|
+
* import { getPositionInvestedCount } from "backtest-kit";
|
|
32639
|
+
*
|
|
32640
|
+
* const count = await getPositionInvestedCount("BTCUSDT");
|
|
32641
|
+
* // No DCA: count === 1
|
|
32642
|
+
* // After one DCA: count === 2
|
|
32643
|
+
* ```
|
|
32644
|
+
*/
|
|
32527
32645
|
async function getPositionInvestedCount(symbol) {
|
|
32528
32646
|
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
|
|
32529
32647
|
symbol,
|
|
@@ -32538,6 +32656,28 @@ async function getPositionInvestedCount(symbol) {
|
|
|
32538
32656
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32539
32657
|
return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32540
32658
|
}
|
|
32659
|
+
/**
|
|
32660
|
+
* Returns the total invested cost basis in dollars for the current pending signal.
|
|
32661
|
+
*
|
|
32662
|
+
* Equal to the sum of all _entry costs (Σ entry.cost).
|
|
32663
|
+
* Each entry cost is set at the time of commitAverageBuy (defaults to CC_POSITION_ENTRY_COST).
|
|
32664
|
+
*
|
|
32665
|
+
* Returns null if no pending signal exists.
|
|
32666
|
+
*
|
|
32667
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32668
|
+
*
|
|
32669
|
+
* @param symbol - Trading pair symbol
|
|
32670
|
+
* @returns Promise resolving to total invested cost in dollars or null
|
|
32671
|
+
*
|
|
32672
|
+
* @example
|
|
32673
|
+
* ```typescript
|
|
32674
|
+
* import { getPositionInvestedCost } from "backtest-kit";
|
|
32675
|
+
*
|
|
32676
|
+
* const cost = await getPositionInvestedCost("BTCUSDT");
|
|
32677
|
+
* // No DCA, default cost: cost === 100
|
|
32678
|
+
* // After one DCA with default cost: cost === 200
|
|
32679
|
+
* ```
|
|
32680
|
+
*/
|
|
32541
32681
|
async function getPositionInvestedCost(symbol) {
|
|
32542
32682
|
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
|
|
32543
32683
|
symbol,
|
|
@@ -32552,6 +32692,29 @@ async function getPositionInvestedCost(symbol) {
|
|
|
32552
32692
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32553
32693
|
return await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32554
32694
|
}
|
|
32695
|
+
/**
|
|
32696
|
+
* Returns the unrealized PNL percentage for the current pending signal at current market price.
|
|
32697
|
+
*
|
|
32698
|
+
* Accounts for partial closes, DCA entries, slippage and fees
|
|
32699
|
+
* (delegates to toProfitLossDto).
|
|
32700
|
+
*
|
|
32701
|
+
* Returns null if no pending signal exists.
|
|
32702
|
+
*
|
|
32703
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32704
|
+
* Automatically fetches current price via getAveragePrice.
|
|
32705
|
+
*
|
|
32706
|
+
* @param symbol - Trading pair symbol
|
|
32707
|
+
* @returns Promise resolving to PNL percentage or null
|
|
32708
|
+
*
|
|
32709
|
+
* @example
|
|
32710
|
+
* ```typescript
|
|
32711
|
+
* import { getPositionPnlPercent } from "backtest-kit";
|
|
32712
|
+
*
|
|
32713
|
+
* const pnlPct = await getPositionPnlPercent("BTCUSDT");
|
|
32714
|
+
* // LONG at 100, current=105: pnlPct ≈ 5
|
|
32715
|
+
* // LONG at 100, current=95: pnlPct ≈ -5
|
|
32716
|
+
* ```
|
|
32717
|
+
*/
|
|
32555
32718
|
async function getPositionPnlPercent(symbol) {
|
|
32556
32719
|
bt.loggerService.info(GET_POSITION_PNL_PERCENT_METHOD_NAME, { symbol });
|
|
32557
32720
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32701,6 +32864,29 @@ async function commitPartialLossCost(symbol, dollarAmount) {
|
|
|
32701
32864
|
});
|
|
32702
32865
|
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
32703
32866
|
}
|
|
32867
|
+
/**
|
|
32868
|
+
* Returns the unrealized PNL in dollars for the current pending signal at current market price.
|
|
32869
|
+
*
|
|
32870
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost.
|
|
32871
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
32872
|
+
*
|
|
32873
|
+
* Returns null if no pending signal exists.
|
|
32874
|
+
*
|
|
32875
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32876
|
+
* Automatically fetches current price via getAveragePrice.
|
|
32877
|
+
*
|
|
32878
|
+
* @param symbol - Trading pair symbol
|
|
32879
|
+
* @returns Promise resolving to PNL in dollars or null
|
|
32880
|
+
*
|
|
32881
|
+
* @example
|
|
32882
|
+
* ```typescript
|
|
32883
|
+
* import { getPositionPnlCost } from "backtest-kit";
|
|
32884
|
+
*
|
|
32885
|
+
* const pnlCost = await getPositionPnlCost("BTCUSDT");
|
|
32886
|
+
* // LONG at 100, invested $100, current=105: pnlCost ≈ 5
|
|
32887
|
+
* // LONG at 100, invested $200 (DCA), current=95: pnlCost ≈ -10
|
|
32888
|
+
* ```
|
|
32889
|
+
*/
|
|
32704
32890
|
async function getPositionPnlCost(symbol) {
|
|
32705
32891
|
bt.loggerService.info(GET_POSITION_PNL_COST_METHOD_NAME, { symbol });
|
|
32706
32892
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32787,6 +32973,104 @@ async function getPositionPartials(symbol) {
|
|
|
32787
32973
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32788
32974
|
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32789
32975
|
}
|
|
32976
|
+
/**
|
|
32977
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
32978
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
32979
|
+
*
|
|
32980
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
32981
|
+
* where step = level * percent / 100.
|
|
32982
|
+
* Returns false if no pending signal exists.
|
|
32983
|
+
*
|
|
32984
|
+
* @param symbol - Trading pair symbol
|
|
32985
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
32986
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
32987
|
+
* @returns Promise<boolean> - true if price overlaps an existing entry level (DCA not recommended)
|
|
32988
|
+
*
|
|
32989
|
+
* @example
|
|
32990
|
+
* ```typescript
|
|
32991
|
+
* import { getPositionEntryOverlap } from "backtest-kit";
|
|
32992
|
+
*
|
|
32993
|
+
* // LONG with levels [43000, 42000], check if 42100 is too close to 42000
|
|
32994
|
+
* const overlap = await getPositionEntryOverlap("BTCUSDT", 42100, { upperPercent: 5, lowerPercent: 5 });
|
|
32995
|
+
* // overlap = true (42100 is within 5% of 42000 = [39900, 44100])
|
|
32996
|
+
* if (!overlap) {
|
|
32997
|
+
* await commitAverageBuy("BTCUSDT");
|
|
32998
|
+
* }
|
|
32999
|
+
* ```
|
|
33000
|
+
*/
|
|
33001
|
+
async function getPositionEntryOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
|
|
33002
|
+
bt.loggerService.info(GET_POSITION_ENTRY_OVERLAP_METHOD_NAME, {
|
|
33003
|
+
symbol,
|
|
33004
|
+
currentPrice,
|
|
33005
|
+
ladder,
|
|
33006
|
+
});
|
|
33007
|
+
if (!ExecutionContextService.hasContext()) {
|
|
33008
|
+
throw new Error("getPositionEntryOverlap requires an execution context");
|
|
33009
|
+
}
|
|
33010
|
+
if (!MethodContextService.hasContext()) {
|
|
33011
|
+
throw new Error("getPositionEntryOverlap requires a method context");
|
|
33012
|
+
}
|
|
33013
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33014
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33015
|
+
const levels = await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33016
|
+
if (!levels) {
|
|
33017
|
+
return false;
|
|
33018
|
+
}
|
|
33019
|
+
return levels.some((level) => {
|
|
33020
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
33021
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
33022
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
33023
|
+
});
|
|
33024
|
+
}
|
|
33025
|
+
/**
|
|
33026
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
33027
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
33028
|
+
*
|
|
33029
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
33030
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
33031
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
33032
|
+
*
|
|
33033
|
+
* @param symbol - Trading pair symbol
|
|
33034
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
33035
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
33036
|
+
* @returns Promise<boolean> - true if price overlaps an existing partial price (partial not recommended)
|
|
33037
|
+
*
|
|
33038
|
+
* @example
|
|
33039
|
+
* ```typescript
|
|
33040
|
+
* import { getPositionPartialOverlap } from "backtest-kit";
|
|
33041
|
+
*
|
|
33042
|
+
* // Partials at [45000], check if 45100 is too close
|
|
33043
|
+
* const overlap = await getPositionPartialOverlap("BTCUSDT", 45100, { upperPercent: 1.5, lowerPercent: 1.5 });
|
|
33044
|
+
* // overlap = true (45100 is within 1.5% of 45000)
|
|
33045
|
+
* if (!overlap) {
|
|
33046
|
+
* await commitPartialProfit("BTCUSDT", 50);
|
|
33047
|
+
* }
|
|
33048
|
+
* ```
|
|
33049
|
+
*/
|
|
33050
|
+
async function getPositionPartialOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
|
|
33051
|
+
bt.loggerService.info(GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME, {
|
|
33052
|
+
symbol,
|
|
33053
|
+
currentPrice,
|
|
33054
|
+
ladder,
|
|
33055
|
+
});
|
|
33056
|
+
if (!ExecutionContextService.hasContext()) {
|
|
33057
|
+
throw new Error("getPositionPartialOverlap requires an execution context");
|
|
33058
|
+
}
|
|
33059
|
+
if (!MethodContextService.hasContext()) {
|
|
33060
|
+
throw new Error("getPositionPartialOverlap requires a method context");
|
|
33061
|
+
}
|
|
33062
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33063
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33064
|
+
const partials = await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33065
|
+
if (!partials) {
|
|
33066
|
+
return false;
|
|
33067
|
+
}
|
|
33068
|
+
return partials.some((partial) => {
|
|
33069
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
33070
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
33071
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
33072
|
+
});
|
|
33073
|
+
}
|
|
32790
33074
|
|
|
32791
33075
|
const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
|
|
32792
33076
|
/**
|
|
@@ -34012,6 +34296,9 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPosition
|
|
|
34012
34296
|
const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnlCost";
|
|
34013
34297
|
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
34014
34298
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
34299
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
|
|
34300
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
34301
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
34015
34302
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
34016
34303
|
const BACKTEST_METHOD_NAME_CANCEL_SCHEDULED = "Backtest.commitCancelScheduled";
|
|
34017
34304
|
const BACKTEST_METHOD_NAME_CLOSE_PENDING = "Backtest.commitClosePending";
|
|
@@ -34767,6 +35054,125 @@ class BacktestUtils {
|
|
|
34767
35054
|
}
|
|
34768
35055
|
return await bt.strategyCoreService.getPositionPartials(true, symbol, context);
|
|
34769
35056
|
};
|
|
35057
|
+
/**
|
|
35058
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
35059
|
+
*
|
|
35060
|
+
* Each element represents a single position entry — the initial open or a subsequent
|
|
35061
|
+
* DCA entry added via commitAverageBuy.
|
|
35062
|
+
*
|
|
35063
|
+
* Returns null if no pending signal exists.
|
|
35064
|
+
* Returns a single-element array if no DCA entries were made.
|
|
35065
|
+
*
|
|
35066
|
+
* Each entry contains:
|
|
35067
|
+
* - `price` — execution price of this entry
|
|
35068
|
+
* - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
|
|
35069
|
+
*
|
|
35070
|
+
* @param symbol - Trading pair symbol
|
|
35071
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35072
|
+
* @returns Array of entry records, or null if no active position
|
|
35073
|
+
*/
|
|
35074
|
+
this.getPositionEntries = async (symbol, context) => {
|
|
35075
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES, {
|
|
35076
|
+
symbol,
|
|
35077
|
+
context,
|
|
35078
|
+
});
|
|
35079
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES);
|
|
35080
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES);
|
|
35081
|
+
{
|
|
35082
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35083
|
+
riskName &&
|
|
35084
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES);
|
|
35085
|
+
riskList &&
|
|
35086
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES));
|
|
35087
|
+
actions &&
|
|
35088
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES));
|
|
35089
|
+
}
|
|
35090
|
+
return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
|
|
35091
|
+
};
|
|
35092
|
+
/**
|
|
35093
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
35094
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
35095
|
+
*
|
|
35096
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
35097
|
+
* where step = level * percent / 100.
|
|
35098
|
+
* Returns false if no pending signal exists.
|
|
35099
|
+
*
|
|
35100
|
+
* @param symbol - Trading pair symbol
|
|
35101
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
35102
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35103
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
35104
|
+
* @returns true if price overlaps an existing entry level (DCA not recommended)
|
|
35105
|
+
*/
|
|
35106
|
+
this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
35107
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
|
|
35108
|
+
symbol,
|
|
35109
|
+
currentPrice,
|
|
35110
|
+
context,
|
|
35111
|
+
ladder,
|
|
35112
|
+
});
|
|
35113
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35114
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35115
|
+
{
|
|
35116
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35117
|
+
riskName &&
|
|
35118
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35119
|
+
riskList &&
|
|
35120
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
35121
|
+
actions &&
|
|
35122
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
35123
|
+
}
|
|
35124
|
+
const levels = await bt.strategyCoreService.getPositionLevels(true, symbol, context);
|
|
35125
|
+
if (!levels) {
|
|
35126
|
+
return false;
|
|
35127
|
+
}
|
|
35128
|
+
return levels.some((level) => {
|
|
35129
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
35130
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
35131
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
35132
|
+
});
|
|
35133
|
+
};
|
|
35134
|
+
/**
|
|
35135
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
35136
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
35137
|
+
*
|
|
35138
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
35139
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
35140
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
35141
|
+
*
|
|
35142
|
+
* @param symbol - Trading pair symbol
|
|
35143
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
35144
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35145
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
35146
|
+
* @returns true if price overlaps an existing partial price (partial not recommended)
|
|
35147
|
+
*/
|
|
35148
|
+
this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
35149
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
|
|
35150
|
+
symbol,
|
|
35151
|
+
currentPrice,
|
|
35152
|
+
context,
|
|
35153
|
+
ladder,
|
|
35154
|
+
});
|
|
35155
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35156
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35157
|
+
{
|
|
35158
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35159
|
+
riskName &&
|
|
35160
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35161
|
+
riskList &&
|
|
35162
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
35163
|
+
actions &&
|
|
35164
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
35165
|
+
}
|
|
35166
|
+
const partials = await bt.strategyCoreService.getPositionPartials(true, symbol, context);
|
|
35167
|
+
if (!partials) {
|
|
35168
|
+
return false;
|
|
35169
|
+
}
|
|
35170
|
+
return partials.some((partial) => {
|
|
35171
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
35172
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
35173
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
35174
|
+
});
|
|
35175
|
+
};
|
|
34770
35176
|
/**
|
|
34771
35177
|
* Stops the strategy from generating new signals.
|
|
34772
35178
|
*
|
|
@@ -35256,6 +35662,7 @@ class BacktestUtils {
|
|
|
35256
35662
|
percentShift,
|
|
35257
35663
|
currentPrice,
|
|
35258
35664
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
35665
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35259
35666
|
position: signal.position,
|
|
35260
35667
|
context,
|
|
35261
35668
|
backtest: true,
|
|
@@ -35340,6 +35747,7 @@ class BacktestUtils {
|
|
|
35340
35747
|
percentShift,
|
|
35341
35748
|
currentPrice,
|
|
35342
35749
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
35750
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35343
35751
|
position: signal.position,
|
|
35344
35752
|
context,
|
|
35345
35753
|
backtest: true,
|
|
@@ -35394,6 +35802,7 @@ class BacktestUtils {
|
|
|
35394
35802
|
currentPrice,
|
|
35395
35803
|
newStopLossPrice,
|
|
35396
35804
|
position: signal.position,
|
|
35805
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35397
35806
|
context,
|
|
35398
35807
|
backtest: true,
|
|
35399
35808
|
});
|
|
@@ -35447,6 +35856,7 @@ class BacktestUtils {
|
|
|
35447
35856
|
currentPrice,
|
|
35448
35857
|
newTakeProfitPrice,
|
|
35449
35858
|
position: signal.position,
|
|
35859
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35450
35860
|
context,
|
|
35451
35861
|
backtest: true,
|
|
35452
35862
|
});
|
|
@@ -35789,6 +36199,9 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPerce
|
|
|
35789
36199
|
const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
35790
36200
|
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
35791
36201
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
36202
|
+
const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
|
|
36203
|
+
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
36204
|
+
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
35792
36205
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
35793
36206
|
const LIVE_METHOD_NAME_CANCEL_SCHEDULED = "Live.cancelScheduled";
|
|
35794
36207
|
const LIVE_METHOD_NAME_CLOSE_PENDING = "Live.closePending";
|
|
@@ -36603,6 +37016,137 @@ class LiveUtils {
|
|
|
36603
37016
|
frameName: "",
|
|
36604
37017
|
});
|
|
36605
37018
|
};
|
|
37019
|
+
/**
|
|
37020
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
37021
|
+
*
|
|
37022
|
+
* Each element represents a single position entry — the initial open or a subsequent
|
|
37023
|
+
* DCA entry added via commitAverageBuy.
|
|
37024
|
+
*
|
|
37025
|
+
* Returns null if no pending signal exists.
|
|
37026
|
+
* Returns a single-element array if no DCA entries were made.
|
|
37027
|
+
*
|
|
37028
|
+
* Each entry contains:
|
|
37029
|
+
* - `price` — execution price of this entry
|
|
37030
|
+
* - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
|
|
37031
|
+
*
|
|
37032
|
+
* @param symbol - Trading pair symbol
|
|
37033
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37034
|
+
* @returns Array of entry records, or null if no active position
|
|
37035
|
+
*/
|
|
37036
|
+
this.getPositionEntries = async (symbol, context) => {
|
|
37037
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ENTRIES, {
|
|
37038
|
+
symbol,
|
|
37039
|
+
context,
|
|
37040
|
+
});
|
|
37041
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ENTRIES);
|
|
37042
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ENTRIES);
|
|
37043
|
+
{
|
|
37044
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37045
|
+
riskName &&
|
|
37046
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRIES);
|
|
37047
|
+
riskList &&
|
|
37048
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRIES));
|
|
37049
|
+
actions &&
|
|
37050
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ENTRIES));
|
|
37051
|
+
}
|
|
37052
|
+
return await bt.strategyCoreService.getPositionEntries(false, symbol, {
|
|
37053
|
+
strategyName: context.strategyName,
|
|
37054
|
+
exchangeName: context.exchangeName,
|
|
37055
|
+
frameName: "",
|
|
37056
|
+
});
|
|
37057
|
+
};
|
|
37058
|
+
/**
|
|
37059
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
37060
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
37061
|
+
*
|
|
37062
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
37063
|
+
* where step = level * percent / 100.
|
|
37064
|
+
* Returns false if no pending signal exists.
|
|
37065
|
+
*
|
|
37066
|
+
* @param symbol - Trading pair symbol
|
|
37067
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
37068
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37069
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
37070
|
+
* @returns true if price overlaps an existing entry level (DCA not recommended)
|
|
37071
|
+
*/
|
|
37072
|
+
this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
37073
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
|
|
37074
|
+
symbol,
|
|
37075
|
+
currentPrice,
|
|
37076
|
+
context,
|
|
37077
|
+
ladder,
|
|
37078
|
+
});
|
|
37079
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37080
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37081
|
+
{
|
|
37082
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37083
|
+
riskName &&
|
|
37084
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37085
|
+
riskList &&
|
|
37086
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
37087
|
+
actions &&
|
|
37088
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
37089
|
+
}
|
|
37090
|
+
const levels = await bt.strategyCoreService.getPositionLevels(false, symbol, {
|
|
37091
|
+
strategyName: context.strategyName,
|
|
37092
|
+
exchangeName: context.exchangeName,
|
|
37093
|
+
frameName: "",
|
|
37094
|
+
});
|
|
37095
|
+
if (!levels) {
|
|
37096
|
+
return false;
|
|
37097
|
+
}
|
|
37098
|
+
return levels.some((level) => {
|
|
37099
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
37100
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
37101
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
37102
|
+
});
|
|
37103
|
+
};
|
|
37104
|
+
/**
|
|
37105
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
37106
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
37107
|
+
*
|
|
37108
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
37109
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
37110
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
37111
|
+
*
|
|
37112
|
+
* @param symbol - Trading pair symbol
|
|
37113
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
37114
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37115
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
37116
|
+
* @returns true if price overlaps an existing partial price (partial not recommended)
|
|
37117
|
+
*/
|
|
37118
|
+
this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
37119
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
|
|
37120
|
+
symbol,
|
|
37121
|
+
currentPrice,
|
|
37122
|
+
context,
|
|
37123
|
+
ladder,
|
|
37124
|
+
});
|
|
37125
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37126
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37127
|
+
{
|
|
37128
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37129
|
+
riskName &&
|
|
37130
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37131
|
+
riskList &&
|
|
37132
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
37133
|
+
actions &&
|
|
37134
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
37135
|
+
}
|
|
37136
|
+
const partials = await bt.strategyCoreService.getPositionPartials(false, symbol, {
|
|
37137
|
+
strategyName: context.strategyName,
|
|
37138
|
+
exchangeName: context.exchangeName,
|
|
37139
|
+
frameName: "",
|
|
37140
|
+
});
|
|
37141
|
+
if (!partials) {
|
|
37142
|
+
return false;
|
|
37143
|
+
}
|
|
37144
|
+
return partials.some((partial) => {
|
|
37145
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
37146
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
37147
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
37148
|
+
});
|
|
37149
|
+
};
|
|
36606
37150
|
/**
|
|
36607
37151
|
* Stops the strategy from generating new signals.
|
|
36608
37152
|
*
|
|
@@ -37185,6 +37729,7 @@ class LiveUtils {
|
|
|
37185
37729
|
percentShift,
|
|
37186
37730
|
currentPrice,
|
|
37187
37731
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
37732
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37188
37733
|
position: signal.position,
|
|
37189
37734
|
context,
|
|
37190
37735
|
backtest: false,
|
|
@@ -37284,6 +37829,7 @@ class LiveUtils {
|
|
|
37284
37829
|
percentShift,
|
|
37285
37830
|
currentPrice,
|
|
37286
37831
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
37832
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37287
37833
|
position: signal.position,
|
|
37288
37834
|
context,
|
|
37289
37835
|
backtest: false,
|
|
@@ -37353,6 +37899,7 @@ class LiveUtils {
|
|
|
37353
37899
|
percentShift,
|
|
37354
37900
|
currentPrice,
|
|
37355
37901
|
newStopLossPrice,
|
|
37902
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37356
37903
|
position: signal.position,
|
|
37357
37904
|
context,
|
|
37358
37905
|
backtest: false,
|
|
@@ -37422,6 +37969,7 @@ class LiveUtils {
|
|
|
37422
37969
|
percentShift,
|
|
37423
37970
|
currentPrice,
|
|
37424
37971
|
newTakeProfitPrice,
|
|
37972
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37425
37973
|
position: signal.position,
|
|
37426
37974
|
context,
|
|
37427
37975
|
backtest: false,
|
|
@@ -45332,4 +45880,4 @@ const percentValue = (yesterdayValue, todayValue) => {
|
|
|
45332
45880
|
return yesterdayValue / todayValue - 1;
|
|
45333
45881
|
};
|
|
45334
45882
|
|
|
45335
|
-
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, 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, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, 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, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
|
|
45883
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, 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, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, 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, getPositionEntryOverlap, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartialOverlap, 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, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
|