backtest-kit 3.8.1 → 4.0.1
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/README.md +903 -2
- package/build/index.cjs +3011 -138
- package/build/index.mjs +3003 -139
- package/package.json +1 -1
- package/types.d.ts +1648 -35
package/build/index.cjs
CHANGED
|
@@ -7379,6 +7379,52 @@ class ClientStrategy {
|
|
|
7379
7379
|
await PersistSignalAdapter.writeSignalData(this._pendingSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
7380
7380
|
// Commit will be emitted in tick() with correct currentTime
|
|
7381
7381
|
}
|
|
7382
|
+
/**
|
|
7383
|
+
* Validates preconditions for partialProfit without mutating state.
|
|
7384
|
+
*
|
|
7385
|
+
* Returns false (never throws) when any condition would cause partialProfit to fail or skip.
|
|
7386
|
+
* Use this to pre-check before calling partialProfit to avoid needing to handle exceptions.
|
|
7387
|
+
*
|
|
7388
|
+
* @param symbol - Trading pair symbol
|
|
7389
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
7390
|
+
* @param currentPrice - Current market price (must be in profit direction)
|
|
7391
|
+
* @returns boolean - true if partialProfit would execute, false otherwise
|
|
7392
|
+
*/
|
|
7393
|
+
async validatePartialProfit(symbol, percentToClose, currentPrice) {
|
|
7394
|
+
this.params.logger.debug("ClientStrategy validatePartialProfit", {
|
|
7395
|
+
symbol,
|
|
7396
|
+
percentToClose,
|
|
7397
|
+
currentPrice,
|
|
7398
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7399
|
+
});
|
|
7400
|
+
if (!this._pendingSignal)
|
|
7401
|
+
return false;
|
|
7402
|
+
if (typeof percentToClose !== "number" || !isFinite(percentToClose))
|
|
7403
|
+
return false;
|
|
7404
|
+
if (percentToClose <= 0)
|
|
7405
|
+
return false;
|
|
7406
|
+
if (percentToClose > 100)
|
|
7407
|
+
return false;
|
|
7408
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7409
|
+
return false;
|
|
7410
|
+
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7411
|
+
if (this._pendingSignal.position === "long" && currentPrice <= effectivePriceOpen)
|
|
7412
|
+
return false;
|
|
7413
|
+
if (this._pendingSignal.position === "short" && currentPrice >= effectivePriceOpen)
|
|
7414
|
+
return false;
|
|
7415
|
+
const effectiveTakeProfit = this._pendingSignal._trailingPriceTakeProfit ?? this._pendingSignal.priceTakeProfit;
|
|
7416
|
+
if (this._pendingSignal.position === "long" && currentPrice >= effectiveTakeProfit)
|
|
7417
|
+
return false;
|
|
7418
|
+
if (this._pendingSignal.position === "short" && currentPrice <= effectiveTakeProfit)
|
|
7419
|
+
return false;
|
|
7420
|
+
const { totalClosedPercent, remainingCostBasis } = getTotalClosed(this._pendingSignal);
|
|
7421
|
+
const totalInvested = (this._pendingSignal._entry ?? []).reduce((s, e) => s + e.cost, 0) || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST;
|
|
7422
|
+
const newPartialDollar = (percentToClose / 100) * remainingCostBasis;
|
|
7423
|
+
const newTotalClosedDollar = (totalClosedPercent / 100) * totalInvested + newPartialDollar;
|
|
7424
|
+
if (newTotalClosedDollar > totalInvested)
|
|
7425
|
+
return false;
|
|
7426
|
+
return true;
|
|
7427
|
+
}
|
|
7382
7428
|
/**
|
|
7383
7429
|
* Executes partial close at profit level (moving toward TP).
|
|
7384
7430
|
*
|
|
@@ -7514,6 +7560,52 @@ class ClientStrategy {
|
|
|
7514
7560
|
});
|
|
7515
7561
|
return true;
|
|
7516
7562
|
}
|
|
7563
|
+
/**
|
|
7564
|
+
* Validates preconditions for partialLoss without mutating state.
|
|
7565
|
+
*
|
|
7566
|
+
* Returns false (never throws) when any condition would cause partialLoss to fail or skip.
|
|
7567
|
+
* Use this to pre-check before calling partialLoss to avoid needing to handle exceptions.
|
|
7568
|
+
*
|
|
7569
|
+
* @param symbol - Trading pair symbol
|
|
7570
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
7571
|
+
* @param currentPrice - Current market price (must be in loss direction)
|
|
7572
|
+
* @returns boolean - true if partialLoss would execute, false otherwise
|
|
7573
|
+
*/
|
|
7574
|
+
async validatePartialLoss(symbol, percentToClose, currentPrice) {
|
|
7575
|
+
this.params.logger.debug("ClientStrategy validatePartialLoss", {
|
|
7576
|
+
symbol,
|
|
7577
|
+
percentToClose,
|
|
7578
|
+
currentPrice,
|
|
7579
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7580
|
+
});
|
|
7581
|
+
if (!this._pendingSignal)
|
|
7582
|
+
return false;
|
|
7583
|
+
if (typeof percentToClose !== "number" || !isFinite(percentToClose))
|
|
7584
|
+
return false;
|
|
7585
|
+
if (percentToClose <= 0)
|
|
7586
|
+
return false;
|
|
7587
|
+
if (percentToClose > 100)
|
|
7588
|
+
return false;
|
|
7589
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7590
|
+
return false;
|
|
7591
|
+
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7592
|
+
if (this._pendingSignal.position === "long" && currentPrice >= effectivePriceOpen)
|
|
7593
|
+
return false;
|
|
7594
|
+
if (this._pendingSignal.position === "short" && currentPrice <= effectivePriceOpen)
|
|
7595
|
+
return false;
|
|
7596
|
+
const effectiveStopLoss = this._pendingSignal._trailingPriceStopLoss ?? this._pendingSignal.priceStopLoss;
|
|
7597
|
+
if (this._pendingSignal.position === "long" && currentPrice <= effectiveStopLoss)
|
|
7598
|
+
return false;
|
|
7599
|
+
if (this._pendingSignal.position === "short" && currentPrice >= effectiveStopLoss)
|
|
7600
|
+
return false;
|
|
7601
|
+
const { totalClosedPercent, remainingCostBasis } = getTotalClosed(this._pendingSignal);
|
|
7602
|
+
const totalInvested = (this._pendingSignal._entry ?? []).reduce((s, e) => s + e.cost, 0) || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST;
|
|
7603
|
+
const newPartialDollar = (percentToClose / 100) * remainingCostBasis;
|
|
7604
|
+
const newTotalClosedDollar = (totalClosedPercent / 100) * totalInvested + newPartialDollar;
|
|
7605
|
+
if (newTotalClosedDollar > totalInvested)
|
|
7606
|
+
return false;
|
|
7607
|
+
return true;
|
|
7608
|
+
}
|
|
7517
7609
|
/**
|
|
7518
7610
|
* Executes partial close at loss level (moving toward SL).
|
|
7519
7611
|
*
|
|
@@ -7649,6 +7741,82 @@ class ClientStrategy {
|
|
|
7649
7741
|
});
|
|
7650
7742
|
return true;
|
|
7651
7743
|
}
|
|
7744
|
+
/**
|
|
7745
|
+
* Validates preconditions for breakeven without mutating state.
|
|
7746
|
+
*
|
|
7747
|
+
* Returns false (never throws) when any condition would cause breakeven to fail or skip.
|
|
7748
|
+
* Mirrors the full BREAKEVEN_FN condition logic including threshold, trailing state and intrusion checks.
|
|
7749
|
+
*
|
|
7750
|
+
* @param symbol - Trading pair symbol
|
|
7751
|
+
* @param currentPrice - Current market price to check threshold
|
|
7752
|
+
* @returns boolean - true if breakeven would execute, false otherwise
|
|
7753
|
+
*/
|
|
7754
|
+
async validateBreakeven(symbol, currentPrice) {
|
|
7755
|
+
this.params.logger.debug("ClientStrategy validateBreakeven", {
|
|
7756
|
+
symbol,
|
|
7757
|
+
currentPrice,
|
|
7758
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7759
|
+
});
|
|
7760
|
+
if (!this._pendingSignal)
|
|
7761
|
+
return false;
|
|
7762
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7763
|
+
return false;
|
|
7764
|
+
const signal = this._pendingSignal;
|
|
7765
|
+
const breakevenPrice = getEffectivePriceOpen(signal);
|
|
7766
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
7767
|
+
if (signal.position === "long" && breakevenPrice >= effectiveTakeProfit)
|
|
7768
|
+
return false;
|
|
7769
|
+
if (signal.position === "short" && breakevenPrice <= effectiveTakeProfit)
|
|
7770
|
+
return false;
|
|
7771
|
+
const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2;
|
|
7772
|
+
if (signal._trailingPriceStopLoss !== undefined) {
|
|
7773
|
+
const trailingStopLoss = signal._trailingPriceStopLoss;
|
|
7774
|
+
if (signal.position === "long") {
|
|
7775
|
+
const isPositiveTrailing = trailingStopLoss > breakevenPrice;
|
|
7776
|
+
if (isPositiveTrailing)
|
|
7777
|
+
return true; // already protecting profit
|
|
7778
|
+
const thresholdPrice = breakevenPrice * (1 + breakevenThresholdPercent / 100);
|
|
7779
|
+
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
7780
|
+
if (!isThresholdReached || breakevenPrice <= trailingStopLoss)
|
|
7781
|
+
return false;
|
|
7782
|
+
if (currentPrice < breakevenPrice)
|
|
7783
|
+
return false; // price intrusion
|
|
7784
|
+
return true;
|
|
7785
|
+
}
|
|
7786
|
+
else {
|
|
7787
|
+
const isPositiveTrailing = trailingStopLoss < breakevenPrice;
|
|
7788
|
+
if (isPositiveTrailing)
|
|
7789
|
+
return true; // already protecting profit
|
|
7790
|
+
const thresholdPrice = breakevenPrice * (1 - breakevenThresholdPercent / 100);
|
|
7791
|
+
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
7792
|
+
if (!isThresholdReached || breakevenPrice >= trailingStopLoss)
|
|
7793
|
+
return false;
|
|
7794
|
+
if (currentPrice > breakevenPrice)
|
|
7795
|
+
return false; // price intrusion
|
|
7796
|
+
return true;
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
const currentStopLoss = signal.priceStopLoss;
|
|
7800
|
+
if (signal.position === "long") {
|
|
7801
|
+
const thresholdPrice = breakevenPrice * (1 + breakevenThresholdPercent / 100);
|
|
7802
|
+
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
7803
|
+
const canMove = isThresholdReached && currentStopLoss < breakevenPrice;
|
|
7804
|
+
if (!canMove)
|
|
7805
|
+
return false;
|
|
7806
|
+
if (currentPrice < breakevenPrice)
|
|
7807
|
+
return false;
|
|
7808
|
+
}
|
|
7809
|
+
else {
|
|
7810
|
+
const thresholdPrice = breakevenPrice * (1 - breakevenThresholdPercent / 100);
|
|
7811
|
+
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
7812
|
+
const canMove = isThresholdReached && currentStopLoss > breakevenPrice;
|
|
7813
|
+
if (!canMove)
|
|
7814
|
+
return false;
|
|
7815
|
+
if (currentPrice > breakevenPrice)
|
|
7816
|
+
return false;
|
|
7817
|
+
}
|
|
7818
|
+
return true;
|
|
7819
|
+
}
|
|
7652
7820
|
/**
|
|
7653
7821
|
* Moves stop-loss to breakeven (entry price) when price reaches threshold.
|
|
7654
7822
|
*
|
|
@@ -7771,6 +7939,65 @@ class ClientStrategy {
|
|
|
7771
7939
|
});
|
|
7772
7940
|
return true;
|
|
7773
7941
|
}
|
|
7942
|
+
/**
|
|
7943
|
+
* Validates preconditions for trailingStop without mutating state.
|
|
7944
|
+
*
|
|
7945
|
+
* Returns false (never throws) when any condition would cause trailingStop to fail or skip.
|
|
7946
|
+
* Includes absorption check: returns false if new SL would not improve on the current trailing SL.
|
|
7947
|
+
*
|
|
7948
|
+
* @param symbol - Trading pair symbol
|
|
7949
|
+
* @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
|
|
7950
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
7951
|
+
* @returns boolean - true if trailingStop would execute, false otherwise
|
|
7952
|
+
*/
|
|
7953
|
+
async validateTrailingStop(symbol, percentShift, currentPrice) {
|
|
7954
|
+
this.params.logger.debug("ClientStrategy validateTrailingStop", {
|
|
7955
|
+
symbol,
|
|
7956
|
+
percentShift,
|
|
7957
|
+
currentPrice,
|
|
7958
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7959
|
+
});
|
|
7960
|
+
if (!this._pendingSignal)
|
|
7961
|
+
return false;
|
|
7962
|
+
if (typeof percentShift !== "number" || !isFinite(percentShift))
|
|
7963
|
+
return false;
|
|
7964
|
+
if (percentShift < -100 || percentShift > 100)
|
|
7965
|
+
return false;
|
|
7966
|
+
if (percentShift === 0)
|
|
7967
|
+
return false;
|
|
7968
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7969
|
+
return false;
|
|
7970
|
+
const signal = this._pendingSignal;
|
|
7971
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
7972
|
+
const slDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
|
|
7973
|
+
const newSlDistancePercent = slDistancePercent + percentShift;
|
|
7974
|
+
let newStopLoss;
|
|
7975
|
+
if (signal.position === "long") {
|
|
7976
|
+
newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
|
|
7977
|
+
}
|
|
7978
|
+
else {
|
|
7979
|
+
newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
|
|
7980
|
+
}
|
|
7981
|
+
// Intrusion check (mirrors trailingStop method: applied before TRAILING_STOP_LOSS_FN, for all calls)
|
|
7982
|
+
if (signal.position === "long" && currentPrice < newStopLoss)
|
|
7983
|
+
return false;
|
|
7984
|
+
if (signal.position === "short" && currentPrice > newStopLoss)
|
|
7985
|
+
return false;
|
|
7986
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
7987
|
+
if (signal.position === "long" && newStopLoss >= effectiveTakeProfit)
|
|
7988
|
+
return false;
|
|
7989
|
+
if (signal.position === "short" && newStopLoss <= effectiveTakeProfit)
|
|
7990
|
+
return false;
|
|
7991
|
+
// Absorption check (mirrors TRAILING_STOP_LOSS_FN: first call is unconditional)
|
|
7992
|
+
const currentTrailingSL = signal._trailingPriceStopLoss;
|
|
7993
|
+
if (currentTrailingSL !== undefined) {
|
|
7994
|
+
if (signal.position === "long" && newStopLoss <= currentTrailingSL)
|
|
7995
|
+
return false;
|
|
7996
|
+
if (signal.position === "short" && newStopLoss >= currentTrailingSL)
|
|
7997
|
+
return false;
|
|
7998
|
+
}
|
|
7999
|
+
return true;
|
|
8000
|
+
}
|
|
7774
8001
|
/**
|
|
7775
8002
|
* Adjusts trailing stop-loss by shifting distance between entry and original SL.
|
|
7776
8003
|
*
|
|
@@ -7959,6 +8186,65 @@ class ClientStrategy {
|
|
|
7959
8186
|
});
|
|
7960
8187
|
return true;
|
|
7961
8188
|
}
|
|
8189
|
+
/**
|
|
8190
|
+
* Validates preconditions for trailingTake without mutating state.
|
|
8191
|
+
*
|
|
8192
|
+
* Returns false (never throws) when any condition would cause trailingTake to fail or skip.
|
|
8193
|
+
* Includes absorption check: returns false if new TP would not improve on the current trailing TP.
|
|
8194
|
+
*
|
|
8195
|
+
* @param symbol - Trading pair symbol
|
|
8196
|
+
* @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
|
|
8197
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
8198
|
+
* @returns boolean - true if trailingTake would execute, false otherwise
|
|
8199
|
+
*/
|
|
8200
|
+
async validateTrailingTake(symbol, percentShift, currentPrice) {
|
|
8201
|
+
this.params.logger.debug("ClientStrategy validateTrailingTake", {
|
|
8202
|
+
symbol,
|
|
8203
|
+
percentShift,
|
|
8204
|
+
currentPrice,
|
|
8205
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
8206
|
+
});
|
|
8207
|
+
if (!this._pendingSignal)
|
|
8208
|
+
return false;
|
|
8209
|
+
if (typeof percentShift !== "number" || !isFinite(percentShift))
|
|
8210
|
+
return false;
|
|
8211
|
+
if (percentShift < -100 || percentShift > 100)
|
|
8212
|
+
return false;
|
|
8213
|
+
if (percentShift === 0)
|
|
8214
|
+
return false;
|
|
8215
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
8216
|
+
return false;
|
|
8217
|
+
const signal = this._pendingSignal;
|
|
8218
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
8219
|
+
const tpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
8220
|
+
const newTpDistancePercent = tpDistancePercent + percentShift;
|
|
8221
|
+
let newTakeProfit;
|
|
8222
|
+
if (signal.position === "long") {
|
|
8223
|
+
newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
|
|
8224
|
+
}
|
|
8225
|
+
else {
|
|
8226
|
+
newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
|
|
8227
|
+
}
|
|
8228
|
+
// Intrusion check (mirrors trailingTake method: applied before TRAILING_TAKE_PROFIT_FN, for all calls)
|
|
8229
|
+
if (signal.position === "long" && currentPrice > newTakeProfit)
|
|
8230
|
+
return false;
|
|
8231
|
+
if (signal.position === "short" && currentPrice < newTakeProfit)
|
|
8232
|
+
return false;
|
|
8233
|
+
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
8234
|
+
if (signal.position === "long" && newTakeProfit <= effectiveStopLoss)
|
|
8235
|
+
return false;
|
|
8236
|
+
if (signal.position === "short" && newTakeProfit >= effectiveStopLoss)
|
|
8237
|
+
return false;
|
|
8238
|
+
// Absorption check (mirrors TRAILING_TAKE_PROFIT_FN: first call is unconditional)
|
|
8239
|
+
const currentTrailingTP = signal._trailingPriceTakeProfit;
|
|
8240
|
+
if (currentTrailingTP !== undefined) {
|
|
8241
|
+
if (signal.position === "long" && newTakeProfit >= currentTrailingTP)
|
|
8242
|
+
return false;
|
|
8243
|
+
if (signal.position === "short" && newTakeProfit <= currentTrailingTP)
|
|
8244
|
+
return false;
|
|
8245
|
+
}
|
|
8246
|
+
return true;
|
|
8247
|
+
}
|
|
7962
8248
|
/**
|
|
7963
8249
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
7964
8250
|
*
|
|
@@ -8133,6 +8419,41 @@ class ClientStrategy {
|
|
|
8133
8419
|
});
|
|
8134
8420
|
return true;
|
|
8135
8421
|
}
|
|
8422
|
+
/**
|
|
8423
|
+
* Validates preconditions for averageBuy without mutating state.
|
|
8424
|
+
*
|
|
8425
|
+
* Returns false (never throws) when any condition would cause averageBuy to fail or skip.
|
|
8426
|
+
*
|
|
8427
|
+
* @param symbol - Trading pair symbol
|
|
8428
|
+
* @param currentPrice - New entry price to add
|
|
8429
|
+
* @returns boolean - true if averageBuy would execute, false otherwise
|
|
8430
|
+
*/
|
|
8431
|
+
async validateAverageBuy(symbol, currentPrice) {
|
|
8432
|
+
this.params.logger.debug("ClientStrategy validateAverageBuy", {
|
|
8433
|
+
symbol,
|
|
8434
|
+
currentPrice,
|
|
8435
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
8436
|
+
});
|
|
8437
|
+
if (!this._pendingSignal)
|
|
8438
|
+
return false;
|
|
8439
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
8440
|
+
return false;
|
|
8441
|
+
const signal = this._pendingSignal;
|
|
8442
|
+
const entries = (!signal._entry || signal._entry.length === 0)
|
|
8443
|
+
? [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST }]
|
|
8444
|
+
: signal._entry;
|
|
8445
|
+
if (signal.position === "long") {
|
|
8446
|
+
const minEntryPrice = Math.min(...entries.map((e) => e.price));
|
|
8447
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_DCA_EVERYWHERE && currentPrice >= minEntryPrice)
|
|
8448
|
+
return false;
|
|
8449
|
+
}
|
|
8450
|
+
else {
|
|
8451
|
+
const maxEntryPrice = Math.max(...entries.map((e) => e.price));
|
|
8452
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_DCA_EVERYWHERE && currentPrice <= maxEntryPrice)
|
|
8453
|
+
return false;
|
|
8454
|
+
}
|
|
8455
|
+
return true;
|
|
8456
|
+
}
|
|
8136
8457
|
/**
|
|
8137
8458
|
* Adds a new averaging entry to an open position (DCA — Dollar Cost Averaging).
|
|
8138
8459
|
*
|
|
@@ -8518,12 +8839,22 @@ const Risk = new RiskUtils();
|
|
|
8518
8839
|
*/
|
|
8519
8840
|
const CREATE_SYNC_FN = (self, strategyName, exchangeName, frameName, backtest) => functoolsKit.trycatch(async (event) => {
|
|
8520
8841
|
if (event.backtest) {
|
|
8521
|
-
return;
|
|
8842
|
+
return true;
|
|
8522
8843
|
}
|
|
8523
8844
|
await syncSubject.next(event);
|
|
8524
8845
|
await self.actionCoreService.signalSync(backtest, event, { strategyName, exchangeName, frameName });
|
|
8525
8846
|
return true;
|
|
8526
8847
|
}, {
|
|
8848
|
+
fallback: (error) => {
|
|
8849
|
+
const message = "StrategyConnectionService CREATE_SYNC_FN thrown. Broker rejected order request";
|
|
8850
|
+
const payload = {
|
|
8851
|
+
error: functoolsKit.errorData(error),
|
|
8852
|
+
message: functoolsKit.getErrorMessage(error),
|
|
8853
|
+
};
|
|
8854
|
+
self.loggerService.warn(message, payload);
|
|
8855
|
+
console.error(message, payload);
|
|
8856
|
+
errorEmitter.next(error);
|
|
8857
|
+
},
|
|
8527
8858
|
defaultValue: false,
|
|
8528
8859
|
});
|
|
8529
8860
|
/**
|
|
@@ -9224,6 +9555,21 @@ class StrategyConnectionService {
|
|
|
9224
9555
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9225
9556
|
await strategy.closePending(symbol, backtest, closeId);
|
|
9226
9557
|
};
|
|
9558
|
+
/**
|
|
9559
|
+
* Checks whether `partialProfit` would succeed without executing it.
|
|
9560
|
+
* Delegates to `ClientStrategy.validatePartialProfit()` — no throws, pure boolean result.
|
|
9561
|
+
*
|
|
9562
|
+
* @param backtest - Whether running in backtest mode
|
|
9563
|
+
* @param symbol - Trading pair symbol
|
|
9564
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
9565
|
+
* @param currentPrice - Current market price to validate against
|
|
9566
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9567
|
+
* @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
|
|
9568
|
+
*/
|
|
9569
|
+
this.validatePartialProfit = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9570
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9571
|
+
return Promise.resolve(strategy.validatePartialProfit(symbol, percentToClose, currentPrice));
|
|
9572
|
+
};
|
|
9227
9573
|
/**
|
|
9228
9574
|
* Executes partial close at profit level (moving toward TP).
|
|
9229
9575
|
*
|
|
@@ -9265,6 +9611,21 @@ class StrategyConnectionService {
|
|
|
9265
9611
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9266
9612
|
return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest);
|
|
9267
9613
|
};
|
|
9614
|
+
/**
|
|
9615
|
+
* Checks whether `partialLoss` would succeed without executing it.
|
|
9616
|
+
* Delegates to `ClientStrategy.validatePartialLoss()` — no throws, pure boolean result.
|
|
9617
|
+
*
|
|
9618
|
+
* @param backtest - Whether running in backtest mode
|
|
9619
|
+
* @param symbol - Trading pair symbol
|
|
9620
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
9621
|
+
* @param currentPrice - Current market price to validate against
|
|
9622
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9623
|
+
* @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
|
|
9624
|
+
*/
|
|
9625
|
+
this.validatePartialLoss = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9626
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9627
|
+
return Promise.resolve(strategy.validatePartialLoss(symbol, percentToClose, currentPrice));
|
|
9628
|
+
};
|
|
9268
9629
|
/**
|
|
9269
9630
|
* Executes partial close at loss level (moving toward SL).
|
|
9270
9631
|
*
|
|
@@ -9306,6 +9667,21 @@ class StrategyConnectionService {
|
|
|
9306
9667
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9307
9668
|
return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest);
|
|
9308
9669
|
};
|
|
9670
|
+
/**
|
|
9671
|
+
* Checks whether `trailingStop` would succeed without executing it.
|
|
9672
|
+
* Delegates to `ClientStrategy.validateTrailingStop()` — no throws, pure boolean result.
|
|
9673
|
+
*
|
|
9674
|
+
* @param backtest - Whether running in backtest mode
|
|
9675
|
+
* @param symbol - Trading pair symbol
|
|
9676
|
+
* @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
|
|
9677
|
+
* @param currentPrice - Current market price to validate against
|
|
9678
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9679
|
+
* @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
|
|
9680
|
+
*/
|
|
9681
|
+
this.validateTrailingStop = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9682
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9683
|
+
return Promise.resolve(strategy.validateTrailingStop(symbol, percentShift, currentPrice));
|
|
9684
|
+
};
|
|
9309
9685
|
/**
|
|
9310
9686
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
9311
9687
|
*
|
|
@@ -9345,6 +9721,21 @@ class StrategyConnectionService {
|
|
|
9345
9721
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9346
9722
|
return await strategy.trailingStop(symbol, percentShift, currentPrice, backtest);
|
|
9347
9723
|
};
|
|
9724
|
+
/**
|
|
9725
|
+
* Checks whether `trailingTake` would succeed without executing it.
|
|
9726
|
+
* Delegates to `ClientStrategy.validateTrailingTake()` — no throws, pure boolean result.
|
|
9727
|
+
*
|
|
9728
|
+
* @param backtest - Whether running in backtest mode
|
|
9729
|
+
* @param symbol - Trading pair symbol
|
|
9730
|
+
* @param percentShift - Percentage adjustment to ORIGINAL TP distance [-100, 100], excluding 0
|
|
9731
|
+
* @param currentPrice - Current market price to validate against
|
|
9732
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9733
|
+
* @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
|
|
9734
|
+
*/
|
|
9735
|
+
this.validateTrailingTake = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9736
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9737
|
+
return Promise.resolve(strategy.validateTrailingTake(symbol, percentShift, currentPrice));
|
|
9738
|
+
};
|
|
9348
9739
|
/**
|
|
9349
9740
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
9350
9741
|
*
|
|
@@ -9384,6 +9775,20 @@ class StrategyConnectionService {
|
|
|
9384
9775
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9385
9776
|
return await strategy.trailingTake(symbol, percentShift, currentPrice, backtest);
|
|
9386
9777
|
};
|
|
9778
|
+
/**
|
|
9779
|
+
* Checks whether `breakeven` would succeed without executing it.
|
|
9780
|
+
* Delegates to `ClientStrategy.validateBreakeven()` — no throws, pure boolean result.
|
|
9781
|
+
*
|
|
9782
|
+
* @param backtest - Whether running in backtest mode
|
|
9783
|
+
* @param symbol - Trading pair symbol
|
|
9784
|
+
* @param currentPrice - Current market price to validate against
|
|
9785
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9786
|
+
* @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
|
|
9787
|
+
*/
|
|
9788
|
+
this.validateBreakeven = (backtest, symbol, currentPrice, context) => {
|
|
9789
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9790
|
+
return Promise.resolve(strategy.validateBreakeven(symbol, currentPrice));
|
|
9791
|
+
};
|
|
9387
9792
|
/**
|
|
9388
9793
|
* Delegates to ClientStrategy.breakeven() with current execution context.
|
|
9389
9794
|
*
|
|
@@ -9449,6 +9854,20 @@ class StrategyConnectionService {
|
|
|
9449
9854
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9450
9855
|
return await strategy.activateScheduled(symbol, backtest, activateId);
|
|
9451
9856
|
};
|
|
9857
|
+
/**
|
|
9858
|
+
* Checks whether `averageBuy` would succeed without executing it.
|
|
9859
|
+
* Delegates to `ClientStrategy.validateAverageBuy()` — no throws, pure boolean result.
|
|
9860
|
+
*
|
|
9861
|
+
* @param backtest - Whether running in backtest mode
|
|
9862
|
+
* @param symbol - Trading pair symbol
|
|
9863
|
+
* @param currentPrice - New entry price to validate
|
|
9864
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9865
|
+
* @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
|
|
9866
|
+
*/
|
|
9867
|
+
this.validateAverageBuy = (backtest, symbol, currentPrice, context) => {
|
|
9868
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9869
|
+
return Promise.resolve(strategy.validateAverageBuy(symbol, currentPrice));
|
|
9870
|
+
};
|
|
9452
9871
|
/**
|
|
9453
9872
|
* Adds a new DCA entry to the active pending signal.
|
|
9454
9873
|
*
|
|
@@ -12807,6 +13226,28 @@ class StrategyCoreService {
|
|
|
12807
13226
|
}
|
|
12808
13227
|
return await this.strategyConnectionService.clear(payload);
|
|
12809
13228
|
};
|
|
13229
|
+
/**
|
|
13230
|
+
* Checks whether `partialProfit` would succeed without executing it.
|
|
13231
|
+
* Validates context, then delegates to StrategyConnectionService.validatePartialProfit().
|
|
13232
|
+
*
|
|
13233
|
+
* @param backtest - Whether running in backtest mode
|
|
13234
|
+
* @param symbol - Trading pair symbol
|
|
13235
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
13236
|
+
* @param currentPrice - Current market price to validate against
|
|
13237
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13238
|
+
* @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
|
|
13239
|
+
*/
|
|
13240
|
+
this.validatePartialProfit = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
13241
|
+
this.loggerService.log("strategyCoreService validatePartialProfit", {
|
|
13242
|
+
symbol,
|
|
13243
|
+
percentToClose,
|
|
13244
|
+
currentPrice,
|
|
13245
|
+
context,
|
|
13246
|
+
backtest,
|
|
13247
|
+
});
|
|
13248
|
+
await this.validate(context);
|
|
13249
|
+
return await this.strategyConnectionService.validatePartialProfit(backtest, symbol, percentToClose, currentPrice, context);
|
|
13250
|
+
};
|
|
12810
13251
|
/**
|
|
12811
13252
|
* Executes partial close at profit level (moving toward TP).
|
|
12812
13253
|
*
|
|
@@ -12848,6 +13289,28 @@ class StrategyCoreService {
|
|
|
12848
13289
|
await this.validate(context);
|
|
12849
13290
|
return await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
|
|
12850
13291
|
};
|
|
13292
|
+
/**
|
|
13293
|
+
* Checks whether `partialLoss` would succeed without executing it.
|
|
13294
|
+
* Validates context, then delegates to StrategyConnectionService.validatePartialLoss().
|
|
13295
|
+
*
|
|
13296
|
+
* @param backtest - Whether running in backtest mode
|
|
13297
|
+
* @param symbol - Trading pair symbol
|
|
13298
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
13299
|
+
* @param currentPrice - Current market price to validate against
|
|
13300
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13301
|
+
* @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
|
|
13302
|
+
*/
|
|
13303
|
+
this.validatePartialLoss = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
13304
|
+
this.loggerService.log("strategyCoreService validatePartialLoss", {
|
|
13305
|
+
symbol,
|
|
13306
|
+
percentToClose,
|
|
13307
|
+
currentPrice,
|
|
13308
|
+
context,
|
|
13309
|
+
backtest,
|
|
13310
|
+
});
|
|
13311
|
+
await this.validate(context);
|
|
13312
|
+
return await this.strategyConnectionService.validatePartialLoss(backtest, symbol, percentToClose, currentPrice, context);
|
|
13313
|
+
};
|
|
12851
13314
|
/**
|
|
12852
13315
|
* Executes partial close at loss level (moving toward SL).
|
|
12853
13316
|
*
|
|
@@ -12917,6 +13380,28 @@ class StrategyCoreService {
|
|
|
12917
13380
|
* );
|
|
12918
13381
|
* ```
|
|
12919
13382
|
*/
|
|
13383
|
+
this.validateTrailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
13384
|
+
this.loggerService.log("strategyCoreService validateTrailingStop", {
|
|
13385
|
+
symbol,
|
|
13386
|
+
percentShift,
|
|
13387
|
+
currentPrice,
|
|
13388
|
+
context,
|
|
13389
|
+
backtest,
|
|
13390
|
+
});
|
|
13391
|
+
await this.validate(context);
|
|
13392
|
+
return await this.strategyConnectionService.validateTrailingStop(backtest, symbol, percentShift, currentPrice, context);
|
|
13393
|
+
};
|
|
13394
|
+
/**
|
|
13395
|
+
* Checks whether `trailingStop` would succeed without executing it.
|
|
13396
|
+
* Validates context, then delegates to StrategyConnectionService.validateTrailingStop().
|
|
13397
|
+
*
|
|
13398
|
+
* @param backtest - Whether running in backtest mode
|
|
13399
|
+
* @param symbol - Trading pair symbol
|
|
13400
|
+
* @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
|
|
13401
|
+
* @param currentPrice - Current market price to validate against
|
|
13402
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13403
|
+
* @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
|
|
13404
|
+
*/
|
|
12920
13405
|
this.trailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
12921
13406
|
this.loggerService.log("strategyCoreService trailingStop", {
|
|
12922
13407
|
symbol,
|
|
@@ -12952,6 +13437,28 @@ class StrategyCoreService {
|
|
|
12952
13437
|
* );
|
|
12953
13438
|
* ```
|
|
12954
13439
|
*/
|
|
13440
|
+
this.validateTrailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
13441
|
+
this.loggerService.log("strategyCoreService validateTrailingTake", {
|
|
13442
|
+
symbol,
|
|
13443
|
+
percentShift,
|
|
13444
|
+
currentPrice,
|
|
13445
|
+
context,
|
|
13446
|
+
backtest,
|
|
13447
|
+
});
|
|
13448
|
+
await this.validate(context);
|
|
13449
|
+
return await this.strategyConnectionService.validateTrailingTake(backtest, symbol, percentShift, currentPrice, context);
|
|
13450
|
+
};
|
|
13451
|
+
/**
|
|
13452
|
+
* Checks whether `trailingTake` would succeed without executing it.
|
|
13453
|
+
* Validates context, then delegates to StrategyConnectionService.validateTrailingTake().
|
|
13454
|
+
*
|
|
13455
|
+
* @param backtest - Whether running in backtest mode
|
|
13456
|
+
* @param symbol - Trading pair symbol
|
|
13457
|
+
* @param percentShift - Percentage adjustment to ORIGINAL TP distance [-100, 100], excluding 0
|
|
13458
|
+
* @param currentPrice - Current market price to validate against
|
|
13459
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13460
|
+
* @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
|
|
13461
|
+
*/
|
|
12955
13462
|
this.trailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
12956
13463
|
this.loggerService.log("strategyCoreService trailingTake", {
|
|
12957
13464
|
symbol,
|
|
@@ -12983,6 +13490,26 @@ class StrategyCoreService {
|
|
|
12983
13490
|
* );
|
|
12984
13491
|
* ```
|
|
12985
13492
|
*/
|
|
13493
|
+
this.validateBreakeven = async (backtest, symbol, currentPrice, context) => {
|
|
13494
|
+
this.loggerService.log("strategyCoreService validateBreakeven", {
|
|
13495
|
+
symbol,
|
|
13496
|
+
currentPrice,
|
|
13497
|
+
context,
|
|
13498
|
+
backtest,
|
|
13499
|
+
});
|
|
13500
|
+
await this.validate(context);
|
|
13501
|
+
return await this.strategyConnectionService.validateBreakeven(backtest, symbol, currentPrice, context);
|
|
13502
|
+
};
|
|
13503
|
+
/**
|
|
13504
|
+
* Checks whether `breakeven` would succeed without executing it.
|
|
13505
|
+
* Validates context, then delegates to StrategyConnectionService.validateBreakeven().
|
|
13506
|
+
*
|
|
13507
|
+
* @param backtest - Whether running in backtest mode
|
|
13508
|
+
* @param symbol - Trading pair symbol
|
|
13509
|
+
* @param currentPrice - Current market price to validate against
|
|
13510
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13511
|
+
* @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
|
|
13512
|
+
*/
|
|
12986
13513
|
this.breakeven = async (backtest, symbol, currentPrice, context) => {
|
|
12987
13514
|
this.loggerService.log("strategyCoreService breakeven", {
|
|
12988
13515
|
symbol,
|
|
@@ -13038,6 +13565,26 @@ class StrategyCoreService {
|
|
|
13038
13565
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13039
13566
|
* @returns Promise<boolean> - true if entry added, false if rejected
|
|
13040
13567
|
*/
|
|
13568
|
+
this.validateAverageBuy = async (backtest, symbol, currentPrice, context) => {
|
|
13569
|
+
this.loggerService.log("strategyCoreService validateAverageBuy", {
|
|
13570
|
+
symbol,
|
|
13571
|
+
currentPrice,
|
|
13572
|
+
context,
|
|
13573
|
+
backtest,
|
|
13574
|
+
});
|
|
13575
|
+
await this.validate(context);
|
|
13576
|
+
return await this.strategyConnectionService.validateAverageBuy(backtest, symbol, currentPrice, context);
|
|
13577
|
+
};
|
|
13578
|
+
/**
|
|
13579
|
+
* Checks whether `averageBuy` would succeed without executing it.
|
|
13580
|
+
* Validates context, then delegates to StrategyConnectionService.validateAverageBuy().
|
|
13581
|
+
*
|
|
13582
|
+
* @param backtest - Whether running in backtest mode
|
|
13583
|
+
* @param symbol - Trading pair symbol
|
|
13584
|
+
* @param currentPrice - New entry price to validate
|
|
13585
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13586
|
+
* @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
|
|
13587
|
+
*/
|
|
13041
13588
|
this.averageBuy = async (backtest, symbol, currentPrice, context, cost) => {
|
|
13042
13589
|
this.loggerService.log("strategyCoreService averageBuy", {
|
|
13043
13590
|
symbol,
|
|
@@ -30157,6 +30704,1026 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
|
|
|
30157
30704
|
return (dollarAmount / investedCost) * 100;
|
|
30158
30705
|
};
|
|
30159
30706
|
|
|
30707
|
+
/**
|
|
30708
|
+
* Convert an absolute stop-loss price to a percentShift for `commitTrailingStop`.
|
|
30709
|
+
*
|
|
30710
|
+
* percentShift = newSlDistancePercent - originalSlDistancePercent
|
|
30711
|
+
* where distance = Math.abs((effectivePriceOpen - slPrice) / effectivePriceOpen * 100)
|
|
30712
|
+
*
|
|
30713
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
30714
|
+
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
30715
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30716
|
+
* @returns percentShift to pass to `commitTrailingStop`
|
|
30717
|
+
*
|
|
30718
|
+
* @example
|
|
30719
|
+
* // LONG: entry=100, originalSL=90, desired newSL=95
|
|
30720
|
+
* const shift = slPriceToPercentShift(95, 90, 100); // -5
|
|
30721
|
+
* await commitTrailingStop("BTCUSDT", shift, currentPrice);
|
|
30722
|
+
*/
|
|
30723
|
+
const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectivePriceOpen) => {
|
|
30724
|
+
const originalSlDistancePercent = Math.abs((effectivePriceOpen - originalStopLossPrice) / effectivePriceOpen * 100);
|
|
30725
|
+
const newSlDistancePercent = Math.abs((effectivePriceOpen - newStopLossPrice) / effectivePriceOpen * 100);
|
|
30726
|
+
return newSlDistancePercent - originalSlDistancePercent;
|
|
30727
|
+
};
|
|
30728
|
+
|
|
30729
|
+
/**
|
|
30730
|
+
* Convert an absolute take-profit price to a percentShift for `commitTrailingTake`.
|
|
30731
|
+
*
|
|
30732
|
+
* percentShift = newTpDistancePercent - originalTpDistancePercent
|
|
30733
|
+
* where distance = Math.abs((tpPrice - effectivePriceOpen) / effectivePriceOpen * 100)
|
|
30734
|
+
*
|
|
30735
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
30736
|
+
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
30737
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30738
|
+
* @returns percentShift to pass to `commitTrailingTake`
|
|
30739
|
+
*
|
|
30740
|
+
* @example
|
|
30741
|
+
* // LONG: entry=100, originalTP=110, desired newTP=107
|
|
30742
|
+
* const shift = tpPriceToPercentShift(107, 110, 100); // -3
|
|
30743
|
+
* await commitTrailingTake("BTCUSDT", shift, currentPrice);
|
|
30744
|
+
*/
|
|
30745
|
+
const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effectivePriceOpen) => {
|
|
30746
|
+
const originalTpDistancePercent = Math.abs((originalTakeProfitPrice - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
30747
|
+
const newTpDistancePercent = Math.abs((newTakeProfitPrice - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
30748
|
+
return newTpDistancePercent - originalTpDistancePercent;
|
|
30749
|
+
};
|
|
30750
|
+
|
|
30751
|
+
/**
|
|
30752
|
+
* Convert a percentShift for `commitTrailingStop` back to an absolute stop-loss price.
|
|
30753
|
+
*
|
|
30754
|
+
* Inverse of `slPriceToPercentShift`.
|
|
30755
|
+
*
|
|
30756
|
+
* newSlDistancePercent = originalSlDistancePercent + percentShift
|
|
30757
|
+
* LONG: newStopLossPrice = effectivePriceOpen * (1 - newSlDistancePercent / 100)
|
|
30758
|
+
* SHORT: newStopLossPrice = effectivePriceOpen * (1 + newSlDistancePercent / 100)
|
|
30759
|
+
*
|
|
30760
|
+
* @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
|
|
30761
|
+
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
30762
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30763
|
+
* @param position - Position direction: "long" or "short"
|
|
30764
|
+
* @returns Absolute stop-loss price corresponding to the given percentShift
|
|
30765
|
+
*
|
|
30766
|
+
* @example
|
|
30767
|
+
* // LONG: entry=100, originalSL=90, percentShift=-5
|
|
30768
|
+
* const price = slPercentShiftToPrice(-5, 90, 100, "long"); // 95
|
|
30769
|
+
*/
|
|
30770
|
+
const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePriceOpen, position) => {
|
|
30771
|
+
const originalSlDistancePercent = Math.abs((effectivePriceOpen - originalStopLossPrice) / effectivePriceOpen * 100);
|
|
30772
|
+
const newSlDistancePercent = originalSlDistancePercent + percentShift;
|
|
30773
|
+
return position === "long"
|
|
30774
|
+
? effectivePriceOpen * (1 - newSlDistancePercent / 100)
|
|
30775
|
+
: effectivePriceOpen * (1 + newSlDistancePercent / 100);
|
|
30776
|
+
};
|
|
30777
|
+
|
|
30778
|
+
/**
|
|
30779
|
+
* Convert a percentShift for `commitTrailingTake` back to an absolute take-profit price.
|
|
30780
|
+
*
|
|
30781
|
+
* Inverse of `tpPriceToPercentShift`.
|
|
30782
|
+
*
|
|
30783
|
+
* newTpDistancePercent = originalTpDistancePercent + percentShift
|
|
30784
|
+
* LONG: newTakeProfitPrice = effectivePriceOpen * (1 + newTpDistancePercent / 100)
|
|
30785
|
+
* SHORT: newTakeProfitPrice = effectivePriceOpen * (1 - newTpDistancePercent / 100)
|
|
30786
|
+
*
|
|
30787
|
+
* @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
|
|
30788
|
+
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
30789
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30790
|
+
* @param position - Position direction: "long" or "short"
|
|
30791
|
+
* @returns Absolute take-profit price corresponding to the given percentShift
|
|
30792
|
+
*
|
|
30793
|
+
* @example
|
|
30794
|
+
* // LONG: entry=100, originalTP=110, percentShift=-3
|
|
30795
|
+
* const price = tpPercentShiftToPrice(-3, 110, 100, "long"); // 107
|
|
30796
|
+
*/
|
|
30797
|
+
const tpPercentShiftToPrice = (percentShift, originalTakeProfitPrice, effectivePriceOpen, position) => {
|
|
30798
|
+
const originalTpDistancePercent = Math.abs((originalTakeProfitPrice - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
30799
|
+
const newTpDistancePercent = originalTpDistancePercent + percentShift;
|
|
30800
|
+
return position === "long"
|
|
30801
|
+
? effectivePriceOpen * (1 + newTpDistancePercent / 100)
|
|
30802
|
+
: effectivePriceOpen * (1 - newTpDistancePercent / 100);
|
|
30803
|
+
};
|
|
30804
|
+
|
|
30805
|
+
/**
|
|
30806
|
+
* Compute the dollar cost of a partial close from percentToClose and current invested cost basis.
|
|
30807
|
+
*
|
|
30808
|
+
* cost = (percentToClose / 100) * investedCost
|
|
30809
|
+
*
|
|
30810
|
+
* @param percentToClose - Percentage of position to close (0–100)
|
|
30811
|
+
* @param investedCost - Current invested cost basis (from `getPositionInvestedCost`)
|
|
30812
|
+
* @returns Dollar amount that will be closed
|
|
30813
|
+
*
|
|
30814
|
+
* @example
|
|
30815
|
+
* // Position investedCost=$1000, closing 25%
|
|
30816
|
+
* const cost = percentToCloseCost(25, 1000); // 250
|
|
30817
|
+
*/
|
|
30818
|
+
const percentToCloseCost = (percentToClose, investedCost) => {
|
|
30819
|
+
return (percentToClose / 100) * investedCost;
|
|
30820
|
+
};
|
|
30821
|
+
|
|
30822
|
+
/**
|
|
30823
|
+
* Compute the new stop-loss price for a breakeven operation.
|
|
30824
|
+
*
|
|
30825
|
+
* Breakeven moves the SL to the effective entry price (effectivePriceOpen).
|
|
30826
|
+
* The value is the same regardless of position direction.
|
|
30827
|
+
*
|
|
30828
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30829
|
+
* @returns New stop-loss price equal to the effective entry price
|
|
30830
|
+
*
|
|
30831
|
+
* @example
|
|
30832
|
+
* // LONG: entry=100, SL was 90 → breakeven SL = 100
|
|
30833
|
+
* const newSL = breakevenNewStopLossPrice(100); // 100
|
|
30834
|
+
*
|
|
30835
|
+
* // SHORT: entry=100, SL was 110 → breakeven SL = 100
|
|
30836
|
+
* const newSL = breakevenNewStopLossPrice(100); // 100
|
|
30837
|
+
*/
|
|
30838
|
+
const breakevenNewStopLossPrice = (effectivePriceOpen) => {
|
|
30839
|
+
return effectivePriceOpen;
|
|
30840
|
+
};
|
|
30841
|
+
|
|
30842
|
+
/**
|
|
30843
|
+
* Compute the effective take-profit price for a breakeven operation.
|
|
30844
|
+
*
|
|
30845
|
+
* Breakeven does not change the take-profit. Returns the currently effective TP:
|
|
30846
|
+
* `_trailingPriceTakeProfit` if set (trailing TP active), otherwise `priceTakeProfit`.
|
|
30847
|
+
*
|
|
30848
|
+
* @param priceTakeProfit - Original take-profit price from the pending signal
|
|
30849
|
+
* @param trailingPriceTakeProfit - Trailing take-profit override, or undefined if not set
|
|
30850
|
+
* @returns Effective take-profit price (unchanged by breakeven)
|
|
30851
|
+
*
|
|
30852
|
+
* @example
|
|
30853
|
+
* // No trailing TP set
|
|
30854
|
+
* const newTP = breakevenNewTakeProfitPrice(110, undefined); // 110
|
|
30855
|
+
*
|
|
30856
|
+
* // Trailing TP active
|
|
30857
|
+
* const newTP = breakevenNewTakeProfitPrice(110, 107); // 107
|
|
30858
|
+
*/
|
|
30859
|
+
const breakevenNewTakeProfitPrice = (priceTakeProfit, trailingPriceTakeProfit) => {
|
|
30860
|
+
return trailingPriceTakeProfit ?? priceTakeProfit;
|
|
30861
|
+
};
|
|
30862
|
+
|
|
30863
|
+
const BROKER_METHOD_NAME_COMMIT_SIGNAL_OPEN = "BrokerAdapter.commitSignalOpen";
|
|
30864
|
+
const BROKER_METHOD_NAME_COMMIT_SIGNAL_CLOSE = "BrokerAdapter.commitSignalClose";
|
|
30865
|
+
const BROKER_METHOD_NAME_COMMIT_PARTIAL_PROFIT = "BrokerAdapter.commitPartialProfit";
|
|
30866
|
+
const BROKER_METHOD_NAME_COMMIT_PARTIAL_LOSS = "BrokerAdapter.commitPartialLoss";
|
|
30867
|
+
const BROKER_METHOD_NAME_COMMIT_TRAILING_STOP = "BrokerAdapter.commitTrailingStop";
|
|
30868
|
+
const BROKER_METHOD_NAME_COMMIT_TRAILING_TAKE = "BrokerAdapter.commitTrailingTake";
|
|
30869
|
+
const BROKER_METHOD_NAME_COMMIT_BREAKEVEN = "BrokerAdapter.commitBreakeven";
|
|
30870
|
+
const BROKER_METHOD_NAME_COMMIT_AVERAGE_BUY = "BrokerAdapter.commitAverageBuy";
|
|
30871
|
+
const BROKER_METHOD_NAME_USE_BROKER_ADAPTER = "BrokerAdapter.useBrokerAdapter";
|
|
30872
|
+
const BROKER_METHOD_NAME_ENABLE = "BrokerAdapter.enable";
|
|
30873
|
+
const BROKER_METHOD_NAME_DISABLE = "BrokerAdapter.disable";
|
|
30874
|
+
const BROKER_BASE_METHOD_NAME_WAIT_FOR_INIT = "BrokerBase.waitForInit";
|
|
30875
|
+
const BROKER_BASE_METHOD_NAME_ON_SIGNAL_OPEN = "BrokerBase.onSignalOpenCommit";
|
|
30876
|
+
const BROKER_BASE_METHOD_NAME_ON_SIGNAL_CLOSE = "BrokerBase.onSignalCloseCommit";
|
|
30877
|
+
const BROKER_BASE_METHOD_NAME_ON_PARTIAL_PROFIT = "BrokerBase.onPartialProfitCommit";
|
|
30878
|
+
const BROKER_BASE_METHOD_NAME_ON_PARTIAL_LOSS = "BrokerBase.onPartialLossCommit";
|
|
30879
|
+
const BROKER_BASE_METHOD_NAME_ON_TRAILING_STOP = "BrokerBase.onTrailingStopCommit";
|
|
30880
|
+
const BROKER_BASE_METHOD_NAME_ON_TRAILING_TAKE = "BrokerBase.onTrailingTakeCommit";
|
|
30881
|
+
const BROKER_BASE_METHOD_NAME_ON_BREAKEVEN = "BrokerBase.onBreakevenCommit";
|
|
30882
|
+
const BROKER_BASE_METHOD_NAME_ON_AVERAGE_BUY = "BrokerBase.onAverageBuyCommit";
|
|
30883
|
+
class BrokerProxy {
|
|
30884
|
+
constructor(_instance) {
|
|
30885
|
+
this._instance = _instance;
|
|
30886
|
+
this.waitForInit = functoolsKit.singleshot(async () => {
|
|
30887
|
+
if (this._instance.waitForInit) {
|
|
30888
|
+
await this._instance.waitForInit();
|
|
30889
|
+
}
|
|
30890
|
+
});
|
|
30891
|
+
}
|
|
30892
|
+
async onSignalOpenCommit(payload) {
|
|
30893
|
+
if (this._instance.onSignalOpenCommit) {
|
|
30894
|
+
await this._instance.onSignalOpenCommit(payload);
|
|
30895
|
+
}
|
|
30896
|
+
}
|
|
30897
|
+
async onSignalCloseCommit(payload) {
|
|
30898
|
+
if (this._instance.onSignalCloseCommit) {
|
|
30899
|
+
await this._instance.onSignalCloseCommit(payload);
|
|
30900
|
+
}
|
|
30901
|
+
}
|
|
30902
|
+
async onPartialProfitCommit(payload) {
|
|
30903
|
+
if (this._instance.onPartialProfitCommit) {
|
|
30904
|
+
await this._instance.onPartialProfitCommit(payload);
|
|
30905
|
+
}
|
|
30906
|
+
}
|
|
30907
|
+
async onPartialLossCommit(payload) {
|
|
30908
|
+
if (this._instance.onPartialLossCommit) {
|
|
30909
|
+
await this._instance.onPartialLossCommit(payload);
|
|
30910
|
+
}
|
|
30911
|
+
}
|
|
30912
|
+
async onTrailingStopCommit(payload) {
|
|
30913
|
+
if (this._instance.onTrailingStopCommit) {
|
|
30914
|
+
await this._instance.onTrailingStopCommit(payload);
|
|
30915
|
+
}
|
|
30916
|
+
}
|
|
30917
|
+
async onTrailingTakeCommit(payload) {
|
|
30918
|
+
if (this._instance.onTrailingTakeCommit) {
|
|
30919
|
+
await this._instance.onTrailingTakeCommit(payload);
|
|
30920
|
+
}
|
|
30921
|
+
}
|
|
30922
|
+
async onBreakevenCommit(payload) {
|
|
30923
|
+
if (this._instance.onBreakevenCommit) {
|
|
30924
|
+
await this._instance.onBreakevenCommit(payload);
|
|
30925
|
+
}
|
|
30926
|
+
}
|
|
30927
|
+
async onAverageBuyCommit(payload) {
|
|
30928
|
+
if (this._instance.onAverageBuyCommit) {
|
|
30929
|
+
await this._instance.onAverageBuyCommit(payload);
|
|
30930
|
+
}
|
|
30931
|
+
}
|
|
30932
|
+
}
|
|
30933
|
+
/**
|
|
30934
|
+
* Facade for broker integration — intercepts all commit* operations before DI-core mutations.
|
|
30935
|
+
*
|
|
30936
|
+
* Acts as a transaction control point: if any commit* method throws, the DI-core mutation
|
|
30937
|
+
* is never reached and the state remains unchanged.
|
|
30938
|
+
*
|
|
30939
|
+
* In backtest mode all commit* calls are silently skipped (payload.backtest === true).
|
|
30940
|
+
* In live mode the call is forwarded to the registered IBroker adapter via BrokerProxy.
|
|
30941
|
+
*
|
|
30942
|
+
* signal-open and signal-close events are routed automatically via syncSubject subscription
|
|
30943
|
+
* (activated on `enable()`). All other commit* methods are called explicitly from
|
|
30944
|
+
* Live.ts / Backtest.ts / strategy.ts before the corresponding strategyCoreService call.
|
|
30945
|
+
*
|
|
30946
|
+
* @example
|
|
30947
|
+
* ```typescript
|
|
30948
|
+
* import { Broker } from "backtest-kit";
|
|
30949
|
+
*
|
|
30950
|
+
* // Register a custom broker adapter
|
|
30951
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
30952
|
+
*
|
|
30953
|
+
* // Activate syncSubject subscription (signal-open / signal-close routing)
|
|
30954
|
+
* const dispose = Broker.enable();
|
|
30955
|
+
*
|
|
30956
|
+
* // ... run strategy ...
|
|
30957
|
+
*
|
|
30958
|
+
* // Deactivate when done
|
|
30959
|
+
* Broker.disable();
|
|
30960
|
+
* ```
|
|
30961
|
+
*/
|
|
30962
|
+
class BrokerAdapter {
|
|
30963
|
+
constructor() {
|
|
30964
|
+
this._brokerInstance = null;
|
|
30965
|
+
/**
|
|
30966
|
+
* Forwards a signal-open event to the registered broker adapter.
|
|
30967
|
+
*
|
|
30968
|
+
* Called automatically via syncSubject when `enable()` is active.
|
|
30969
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
30970
|
+
*
|
|
30971
|
+
* @param payload - Signal open details: symbol, cost, position, prices, context, backtest flag
|
|
30972
|
+
*
|
|
30973
|
+
* @example
|
|
30974
|
+
* ```typescript
|
|
30975
|
+
* await Broker.commitSignalOpen({
|
|
30976
|
+
* symbol: "BTCUSDT",
|
|
30977
|
+
* cost: 100,
|
|
30978
|
+
* position: "long",
|
|
30979
|
+
* priceOpen: 50000,
|
|
30980
|
+
* priceTakeProfit: 55000,
|
|
30981
|
+
* priceStopLoss: 48000,
|
|
30982
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
30983
|
+
* backtest: false,
|
|
30984
|
+
* });
|
|
30985
|
+
* ```
|
|
30986
|
+
*/
|
|
30987
|
+
this.commitSignalOpen = async (payload) => {
|
|
30988
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_SIGNAL_OPEN, {
|
|
30989
|
+
symbol: payload.symbol,
|
|
30990
|
+
context: payload.context,
|
|
30991
|
+
});
|
|
30992
|
+
if (!this.enable.hasValue()) {
|
|
30993
|
+
return;
|
|
30994
|
+
}
|
|
30995
|
+
if (payload.backtest) {
|
|
30996
|
+
return;
|
|
30997
|
+
}
|
|
30998
|
+
await this._brokerInstance?.onSignalOpenCommit(payload);
|
|
30999
|
+
};
|
|
31000
|
+
/**
|
|
31001
|
+
* Forwards a signal-close event to the registered broker adapter.
|
|
31002
|
+
*
|
|
31003
|
+
* Called automatically via syncSubject when `enable()` is active.
|
|
31004
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31005
|
+
*
|
|
31006
|
+
* @param payload - Signal close details: symbol, cost, position, currentPrice, pnl, context, backtest flag
|
|
31007
|
+
*
|
|
31008
|
+
* @example
|
|
31009
|
+
* ```typescript
|
|
31010
|
+
* await Broker.commitSignalClose({
|
|
31011
|
+
* symbol: "BTCUSDT",
|
|
31012
|
+
* cost: 100,
|
|
31013
|
+
* position: "long",
|
|
31014
|
+
* currentPrice: 54000,
|
|
31015
|
+
* priceTakeProfit: 55000,
|
|
31016
|
+
* priceStopLoss: 48000,
|
|
31017
|
+
* totalEntries: 2,
|
|
31018
|
+
* totalPartials: 1,
|
|
31019
|
+
* pnl: { profit: 80, loss: 0, volume: 100 },
|
|
31020
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31021
|
+
* backtest: false,
|
|
31022
|
+
* });
|
|
31023
|
+
* ```
|
|
31024
|
+
*/
|
|
31025
|
+
this.commitSignalClose = async (payload) => {
|
|
31026
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_SIGNAL_CLOSE, {
|
|
31027
|
+
symbol: payload.symbol,
|
|
31028
|
+
context: payload.context,
|
|
31029
|
+
});
|
|
31030
|
+
if (!this.enable.hasValue()) {
|
|
31031
|
+
return;
|
|
31032
|
+
}
|
|
31033
|
+
if (payload.backtest) {
|
|
31034
|
+
return;
|
|
31035
|
+
}
|
|
31036
|
+
await this._brokerInstance?.onSignalCloseCommit(payload);
|
|
31037
|
+
};
|
|
31038
|
+
/**
|
|
31039
|
+
* Intercepts a partial-profit close before DI-core mutation.
|
|
31040
|
+
*
|
|
31041
|
+
* Called explicitly from Live.ts / Backtest.ts / strategy.ts after all validations pass,
|
|
31042
|
+
* but before `strategyCoreService.partialProfit()`. If this method throws, the DI mutation
|
|
31043
|
+
* is skipped and state remains unchanged.
|
|
31044
|
+
*
|
|
31045
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31046
|
+
*
|
|
31047
|
+
* @param payload - Partial profit details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest flag
|
|
31048
|
+
*
|
|
31049
|
+
* @example
|
|
31050
|
+
* ```typescript
|
|
31051
|
+
* await Broker.commitPartialProfit({
|
|
31052
|
+
* symbol: "BTCUSDT",
|
|
31053
|
+
* percentToClose: 30,
|
|
31054
|
+
* cost: 30,
|
|
31055
|
+
* currentPrice: 52000,
|
|
31056
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31057
|
+
* backtest: false,
|
|
31058
|
+
* });
|
|
31059
|
+
* ```
|
|
31060
|
+
*/
|
|
31061
|
+
this.commitPartialProfit = async (payload) => {
|
|
31062
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_PARTIAL_PROFIT, {
|
|
31063
|
+
symbol: payload.symbol,
|
|
31064
|
+
context: payload.context,
|
|
31065
|
+
});
|
|
31066
|
+
if (!this.enable.hasValue()) {
|
|
31067
|
+
return;
|
|
31068
|
+
}
|
|
31069
|
+
if (payload.backtest) {
|
|
31070
|
+
return;
|
|
31071
|
+
}
|
|
31072
|
+
await this._brokerInstance?.onPartialProfitCommit(payload);
|
|
31073
|
+
};
|
|
31074
|
+
/**
|
|
31075
|
+
* Intercepts a partial-loss close before DI-core mutation.
|
|
31076
|
+
*
|
|
31077
|
+
* Called explicitly from Live.ts / Backtest.ts / strategy.ts after all validations pass,
|
|
31078
|
+
* but before `strategyCoreService.partialLoss()`. If this method throws, the DI mutation
|
|
31079
|
+
* is skipped and state remains unchanged.
|
|
31080
|
+
*
|
|
31081
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31082
|
+
*
|
|
31083
|
+
* @param payload - Partial loss details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest flag
|
|
31084
|
+
*
|
|
31085
|
+
* @example
|
|
31086
|
+
* ```typescript
|
|
31087
|
+
* await Broker.commitPartialLoss({
|
|
31088
|
+
* symbol: "BTCUSDT",
|
|
31089
|
+
* percentToClose: 40,
|
|
31090
|
+
* cost: 40,
|
|
31091
|
+
* currentPrice: 48500,
|
|
31092
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31093
|
+
* backtest: false,
|
|
31094
|
+
* });
|
|
31095
|
+
* ```
|
|
31096
|
+
*/
|
|
31097
|
+
this.commitPartialLoss = async (payload) => {
|
|
31098
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_PARTIAL_LOSS, {
|
|
31099
|
+
symbol: payload.symbol,
|
|
31100
|
+
context: payload.context,
|
|
31101
|
+
});
|
|
31102
|
+
if (!this.enable.hasValue()) {
|
|
31103
|
+
return;
|
|
31104
|
+
}
|
|
31105
|
+
if (payload.backtest) {
|
|
31106
|
+
return;
|
|
31107
|
+
}
|
|
31108
|
+
await this._brokerInstance?.onPartialLossCommit(payload);
|
|
31109
|
+
};
|
|
31110
|
+
/**
|
|
31111
|
+
* Intercepts a trailing stop-loss update before DI-core mutation.
|
|
31112
|
+
*
|
|
31113
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.trailingStop()`.
|
|
31114
|
+
* `newStopLossPrice` is the absolute price computed from percentShift + original SL + effectivePriceOpen.
|
|
31115
|
+
*
|
|
31116
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31117
|
+
*
|
|
31118
|
+
* @param payload - Trailing stop details: symbol, percentShift, currentPrice, newStopLossPrice, context, backtest flag
|
|
31119
|
+
*
|
|
31120
|
+
* @example
|
|
31121
|
+
* ```typescript
|
|
31122
|
+
* // LONG: entry=100, originalSL=90, percentShift=-5 → newSL=95
|
|
31123
|
+
* await Broker.commitTrailingStop({
|
|
31124
|
+
* symbol: "BTCUSDT",
|
|
31125
|
+
* percentShift: -5,
|
|
31126
|
+
* currentPrice: 102,
|
|
31127
|
+
* newStopLossPrice: 95,
|
|
31128
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31129
|
+
* backtest: false,
|
|
31130
|
+
* });
|
|
31131
|
+
* ```
|
|
31132
|
+
*/
|
|
31133
|
+
this.commitTrailingStop = async (payload) => {
|
|
31134
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_TRAILING_STOP, {
|
|
31135
|
+
symbol: payload.symbol,
|
|
31136
|
+
context: payload.context,
|
|
31137
|
+
});
|
|
31138
|
+
if (!this.enable.hasValue()) {
|
|
31139
|
+
return;
|
|
31140
|
+
}
|
|
31141
|
+
if (payload.backtest) {
|
|
31142
|
+
return;
|
|
31143
|
+
}
|
|
31144
|
+
await this._brokerInstance?.onTrailingStopCommit(payload);
|
|
31145
|
+
};
|
|
31146
|
+
/**
|
|
31147
|
+
* Intercepts a trailing take-profit update before DI-core mutation.
|
|
31148
|
+
*
|
|
31149
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.trailingTake()`.
|
|
31150
|
+
* `newTakeProfitPrice` is the absolute price computed from percentShift + original TP + effectivePriceOpen.
|
|
31151
|
+
*
|
|
31152
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31153
|
+
*
|
|
31154
|
+
* @param payload - Trailing take details: symbol, percentShift, currentPrice, newTakeProfitPrice, context, backtest flag
|
|
31155
|
+
*
|
|
31156
|
+
* @example
|
|
31157
|
+
* ```typescript
|
|
31158
|
+
* // LONG: entry=100, originalTP=110, percentShift=-3 → newTP=107
|
|
31159
|
+
* await Broker.commitTrailingTake({
|
|
31160
|
+
* symbol: "BTCUSDT",
|
|
31161
|
+
* percentShift: -3,
|
|
31162
|
+
* currentPrice: 102,
|
|
31163
|
+
* newTakeProfitPrice: 107,
|
|
31164
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31165
|
+
* backtest: false,
|
|
31166
|
+
* });
|
|
31167
|
+
* ```
|
|
31168
|
+
*/
|
|
31169
|
+
this.commitTrailingTake = async (payload) => {
|
|
31170
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_TRAILING_TAKE, {
|
|
31171
|
+
symbol: payload.symbol,
|
|
31172
|
+
context: payload.context,
|
|
31173
|
+
});
|
|
31174
|
+
if (!this.enable.hasValue()) {
|
|
31175
|
+
return;
|
|
31176
|
+
}
|
|
31177
|
+
if (payload.backtest) {
|
|
31178
|
+
return;
|
|
31179
|
+
}
|
|
31180
|
+
await this._brokerInstance?.onTrailingTakeCommit(payload);
|
|
31181
|
+
};
|
|
31182
|
+
/**
|
|
31183
|
+
* Intercepts a breakeven operation before DI-core mutation.
|
|
31184
|
+
*
|
|
31185
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.breakeven()`.
|
|
31186
|
+
* `newStopLossPrice` equals effectivePriceOpen (entry price).
|
|
31187
|
+
* `newTakeProfitPrice` equals `_trailingPriceTakeProfit ?? priceTakeProfit` (TP is unchanged by breakeven).
|
|
31188
|
+
*
|
|
31189
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31190
|
+
*
|
|
31191
|
+
* @param payload - Breakeven details: symbol, currentPrice, newStopLossPrice, newTakeProfitPrice, context, backtest flag
|
|
31192
|
+
*
|
|
31193
|
+
* @example
|
|
31194
|
+
* ```typescript
|
|
31195
|
+
* // LONG: entry=100, currentPrice=100.5, newSL=100 (entry), newTP=110 (unchanged)
|
|
31196
|
+
* await Broker.commitBreakeven({
|
|
31197
|
+
* symbol: "BTCUSDT",
|
|
31198
|
+
* currentPrice: 100.5,
|
|
31199
|
+
* newStopLossPrice: 100,
|
|
31200
|
+
* newTakeProfitPrice: 110,
|
|
31201
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31202
|
+
* backtest: false,
|
|
31203
|
+
* });
|
|
31204
|
+
* ```
|
|
31205
|
+
*/
|
|
31206
|
+
this.commitBreakeven = async (payload) => {
|
|
31207
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_BREAKEVEN, {
|
|
31208
|
+
symbol: payload.symbol,
|
|
31209
|
+
context: payload.context,
|
|
31210
|
+
});
|
|
31211
|
+
if (!this.enable.hasValue()) {
|
|
31212
|
+
return;
|
|
31213
|
+
}
|
|
31214
|
+
if (payload.backtest) {
|
|
31215
|
+
return;
|
|
31216
|
+
}
|
|
31217
|
+
await this._brokerInstance?.onBreakevenCommit(payload);
|
|
31218
|
+
};
|
|
31219
|
+
/**
|
|
31220
|
+
* Intercepts a DCA average-buy entry before DI-core mutation.
|
|
31221
|
+
*
|
|
31222
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.averageBuy()`.
|
|
31223
|
+
* `currentPrice` is the market price at which the new DCA entry is added.
|
|
31224
|
+
* `cost` is the dollar amount of the new entry (default: CC_POSITION_ENTRY_COST).
|
|
31225
|
+
*
|
|
31226
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31227
|
+
*
|
|
31228
|
+
* @param payload - Average buy details: symbol, currentPrice, cost, context, backtest flag
|
|
31229
|
+
*
|
|
31230
|
+
* @example
|
|
31231
|
+
* ```typescript
|
|
31232
|
+
* // Add DCA entry at current market price
|
|
31233
|
+
* await Broker.commitAverageBuy({
|
|
31234
|
+
* symbol: "BTCUSDT",
|
|
31235
|
+
* currentPrice: 42000,
|
|
31236
|
+
* cost: 100,
|
|
31237
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31238
|
+
* backtest: false,
|
|
31239
|
+
* });
|
|
31240
|
+
* ```
|
|
31241
|
+
*/
|
|
31242
|
+
this.commitAverageBuy = async (payload) => {
|
|
31243
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_AVERAGE_BUY, {
|
|
31244
|
+
symbol: payload.symbol,
|
|
31245
|
+
context: payload.context,
|
|
31246
|
+
});
|
|
31247
|
+
if (!this.enable.hasValue()) {
|
|
31248
|
+
return;
|
|
31249
|
+
}
|
|
31250
|
+
if (payload.backtest) {
|
|
31251
|
+
return;
|
|
31252
|
+
}
|
|
31253
|
+
await this._brokerInstance?.onAverageBuyCommit(payload);
|
|
31254
|
+
};
|
|
31255
|
+
/**
|
|
31256
|
+
* Registers a broker adapter instance or constructor to receive commit* callbacks.
|
|
31257
|
+
*
|
|
31258
|
+
* Must be called before `enable()`. Accepts either a class constructor (called with `new`)
|
|
31259
|
+
* or an already-instantiated object implementing `Partial<IBroker>`.
|
|
31260
|
+
*
|
|
31261
|
+
* @param broker - IBroker constructor or instance
|
|
31262
|
+
*
|
|
31263
|
+
* @example
|
|
31264
|
+
* ```typescript
|
|
31265
|
+
* import { Broker } from "backtest-kit";
|
|
31266
|
+
*
|
|
31267
|
+
* // Register via constructor
|
|
31268
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31269
|
+
*
|
|
31270
|
+
* // Register via instance
|
|
31271
|
+
* Broker.useBrokerAdapter(new MyBrokerAdapter());
|
|
31272
|
+
* ```
|
|
31273
|
+
*/
|
|
31274
|
+
this.useBrokerAdapter = (broker) => {
|
|
31275
|
+
bt.loggerService.info(BROKER_METHOD_NAME_USE_BROKER_ADAPTER, {});
|
|
31276
|
+
if (typeof broker === "function") {
|
|
31277
|
+
const instance = Reflect.construct(broker, []);
|
|
31278
|
+
this._brokerInstance = new BrokerProxy(instance);
|
|
31279
|
+
return;
|
|
31280
|
+
}
|
|
31281
|
+
this._brokerInstance = new BrokerProxy(broker);
|
|
31282
|
+
};
|
|
31283
|
+
/**
|
|
31284
|
+
* Activates the broker: subscribes to syncSubject for signal-open / signal-close routing.
|
|
31285
|
+
*
|
|
31286
|
+
* Must be called after `useBrokerAdapter()`. Returns a dispose function that unsubscribes
|
|
31287
|
+
* from syncSubject (equivalent to calling `disable()`).
|
|
31288
|
+
*
|
|
31289
|
+
* Calling `enable()` without a registered adapter throws immediately.
|
|
31290
|
+
* Calling `enable()` more than once is idempotent (singleshot guard).
|
|
31291
|
+
*
|
|
31292
|
+
* @returns Dispose function — call it to deactivate the broker subscription
|
|
31293
|
+
*
|
|
31294
|
+
* @example
|
|
31295
|
+
* ```typescript
|
|
31296
|
+
* import { Broker } from "backtest-kit";
|
|
31297
|
+
*
|
|
31298
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31299
|
+
* const dispose = Broker.enable();
|
|
31300
|
+
*
|
|
31301
|
+
* // ... run backtest or live session ...
|
|
31302
|
+
*
|
|
31303
|
+
* dispose(); // or Broker.disable()
|
|
31304
|
+
* ```
|
|
31305
|
+
*/
|
|
31306
|
+
this.enable = functoolsKit.singleshot(() => {
|
|
31307
|
+
bt.loggerService.info(BROKER_METHOD_NAME_ENABLE, {});
|
|
31308
|
+
if (!this._brokerInstance) {
|
|
31309
|
+
this.enable.clear();
|
|
31310
|
+
throw new Error("No broker instance provided. Call Broker.useBrokerAdapter first.");
|
|
31311
|
+
}
|
|
31312
|
+
const unSignalOpen = syncSubject.subscribe(async (event) => {
|
|
31313
|
+
if (event.action !== "signal-open") {
|
|
31314
|
+
return;
|
|
31315
|
+
}
|
|
31316
|
+
await this.commitSignalOpen({
|
|
31317
|
+
position: event.signal.position,
|
|
31318
|
+
cost: event.signal.cost,
|
|
31319
|
+
symbol: event.symbol,
|
|
31320
|
+
priceTakeProfit: event.signal.priceTakeProfit,
|
|
31321
|
+
priceStopLoss: event.signal.priceStopLoss,
|
|
31322
|
+
priceOpen: event.signal.priceOpen,
|
|
31323
|
+
context: {
|
|
31324
|
+
strategyName: event.strategyName,
|
|
31325
|
+
exchangeName: event.exchangeName,
|
|
31326
|
+
frameName: event.frameName,
|
|
31327
|
+
},
|
|
31328
|
+
backtest: event.backtest,
|
|
31329
|
+
});
|
|
31330
|
+
});
|
|
31331
|
+
const unSignalClose = syncSubject.subscribe(async (event) => {
|
|
31332
|
+
if (event.action !== "signal-close") {
|
|
31333
|
+
return;
|
|
31334
|
+
}
|
|
31335
|
+
await this.commitSignalClose({
|
|
31336
|
+
position: event.signal.position,
|
|
31337
|
+
currentPrice: event.currentPrice,
|
|
31338
|
+
cost: event.signal.cost,
|
|
31339
|
+
symbol: event.symbol,
|
|
31340
|
+
pnl: event.pnl,
|
|
31341
|
+
totalEntries: event.totalEntries,
|
|
31342
|
+
totalPartials: event.totalPartials,
|
|
31343
|
+
priceStopLoss: event.signal.priceStopLoss,
|
|
31344
|
+
priceTakeProfit: event.signal.priceTakeProfit,
|
|
31345
|
+
context: {
|
|
31346
|
+
strategyName: event.strategyName,
|
|
31347
|
+
exchangeName: event.exchangeName,
|
|
31348
|
+
frameName: event.frameName,
|
|
31349
|
+
},
|
|
31350
|
+
backtest: event.backtest,
|
|
31351
|
+
});
|
|
31352
|
+
});
|
|
31353
|
+
const disposeFn = functoolsKit.compose(() => unSignalOpen(), () => unSignalClose());
|
|
31354
|
+
return () => {
|
|
31355
|
+
this.enable.clear();
|
|
31356
|
+
disposeFn();
|
|
31357
|
+
};
|
|
31358
|
+
});
|
|
31359
|
+
/**
|
|
31360
|
+
* Deactivates the broker: unsubscribes from syncSubject and resets the singleshot guard.
|
|
31361
|
+
*
|
|
31362
|
+
* Idempotent — safe to call even if `enable()` was never called.
|
|
31363
|
+
* After `disable()`, `enable()` can be called again to reactivate.
|
|
31364
|
+
*
|
|
31365
|
+
* @example
|
|
31366
|
+
* ```typescript
|
|
31367
|
+
* import { Broker } from "backtest-kit";
|
|
31368
|
+
*
|
|
31369
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31370
|
+
* Broker.enable();
|
|
31371
|
+
*
|
|
31372
|
+
* // Stop receiving events
|
|
31373
|
+
* Broker.disable();
|
|
31374
|
+
* ```
|
|
31375
|
+
*/
|
|
31376
|
+
this.disable = () => {
|
|
31377
|
+
bt.loggerService.info(BROKER_METHOD_NAME_DISABLE, {});
|
|
31378
|
+
if (this.enable.hasValue()) {
|
|
31379
|
+
const lastSubscription = this.enable();
|
|
31380
|
+
lastSubscription();
|
|
31381
|
+
}
|
|
31382
|
+
};
|
|
31383
|
+
}
|
|
31384
|
+
}
|
|
31385
|
+
/**
|
|
31386
|
+
* Base class for custom broker adapter implementations.
|
|
31387
|
+
*
|
|
31388
|
+
* Provides default no-op implementations for all IBroker methods that log events.
|
|
31389
|
+
* Extend this class to implement a real exchange adapter for:
|
|
31390
|
+
* - Placing and canceling limit/market orders
|
|
31391
|
+
* - Updating stop-loss and take-profit levels on exchange
|
|
31392
|
+
* - Tracking position state in an external system
|
|
31393
|
+
* - Sending trade notifications (Telegram, Discord, Email)
|
|
31394
|
+
* - Recording trades to a database or analytics service
|
|
31395
|
+
*
|
|
31396
|
+
* Key features:
|
|
31397
|
+
* - All methods have default implementations (no need to override unused methods)
|
|
31398
|
+
* - Automatic logging of all events via bt.loggerService
|
|
31399
|
+
* - Implements the full IBroker interface
|
|
31400
|
+
* - `makeExtendable` applied for correct subclass instantiation
|
|
31401
|
+
*
|
|
31402
|
+
* Lifecycle:
|
|
31403
|
+
* 1. Constructor called (no arguments)
|
|
31404
|
+
* 2. `waitForInit()` called once for async initialization (e.g. exchange login)
|
|
31405
|
+
* 3. Event methods called as strategy executes
|
|
31406
|
+
* 4. No explicit dispose — clean up in `waitForInit` teardown or externally
|
|
31407
|
+
*
|
|
31408
|
+
* Event flow (called only in live mode, skipped in backtest):
|
|
31409
|
+
* - `onSignalOpenCommit` — new position opened
|
|
31410
|
+
* - `onSignalCloseCommit` — position closed (SL/TP hit or manual close)
|
|
31411
|
+
* - `onPartialProfitCommit` — partial close at profit executed
|
|
31412
|
+
* - `onPartialLossCommit` — partial close at loss executed
|
|
31413
|
+
* - `onTrailingStopCommit` — trailing stop-loss updated
|
|
31414
|
+
* - `onTrailingTakeCommit` — trailing take-profit updated
|
|
31415
|
+
* - `onBreakevenCommit` — stop-loss moved to entry price
|
|
31416
|
+
* - `onAverageBuyCommit` — new DCA entry added to position
|
|
31417
|
+
*
|
|
31418
|
+
* @example
|
|
31419
|
+
* ```typescript
|
|
31420
|
+
* import { BrokerBase, Broker } from "backtest-kit";
|
|
31421
|
+
*
|
|
31422
|
+
* // Extend BrokerBase and override only needed methods
|
|
31423
|
+
* class BinanceBroker extends BrokerBase {
|
|
31424
|
+
* private client: BinanceClient | null = null;
|
|
31425
|
+
*
|
|
31426
|
+
* async waitForInit() {
|
|
31427
|
+
* super.waitForInit(); // Call parent for logging
|
|
31428
|
+
* this.client = new BinanceClient(process.env.API_KEY, process.env.SECRET);
|
|
31429
|
+
* await this.client.connect();
|
|
31430
|
+
* }
|
|
31431
|
+
*
|
|
31432
|
+
* async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
|
|
31433
|
+
* super.onSignalOpenCommit(payload); // Call parent for logging
|
|
31434
|
+
* await this.client!.placeOrder({
|
|
31435
|
+
* symbol: payload.symbol,
|
|
31436
|
+
* side: payload.position === "long" ? "BUY" : "SELL",
|
|
31437
|
+
* quantity: payload.cost / payload.priceOpen,
|
|
31438
|
+
* });
|
|
31439
|
+
* }
|
|
31440
|
+
*
|
|
31441
|
+
* async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
|
|
31442
|
+
* super.onSignalCloseCommit(payload); // Call parent for logging
|
|
31443
|
+
* await this.client!.closePosition(payload.symbol);
|
|
31444
|
+
* }
|
|
31445
|
+
* }
|
|
31446
|
+
*
|
|
31447
|
+
* // Register the adapter
|
|
31448
|
+
* Broker.useBrokerAdapter(BinanceBroker);
|
|
31449
|
+
* Broker.enable();
|
|
31450
|
+
* ```
|
|
31451
|
+
*
|
|
31452
|
+
* @example
|
|
31453
|
+
* ```typescript
|
|
31454
|
+
* // Minimal implementation — only handle opens and closes
|
|
31455
|
+
* class NotifyBroker extends BrokerBase {
|
|
31456
|
+
* async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
|
|
31457
|
+
* await sendTelegram(`Opened ${payload.position} on ${payload.symbol}`);
|
|
31458
|
+
* }
|
|
31459
|
+
*
|
|
31460
|
+
* async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
|
|
31461
|
+
* const pnl = payload.pnl.profit - payload.pnl.loss;
|
|
31462
|
+
* await sendTelegram(`Closed ${payload.symbol}: PnL $${pnl.toFixed(2)}`);
|
|
31463
|
+
* }
|
|
31464
|
+
* }
|
|
31465
|
+
* ```
|
|
31466
|
+
*/
|
|
31467
|
+
class BrokerBase {
|
|
31468
|
+
/**
|
|
31469
|
+
* Performs async initialization before the broker starts receiving events.
|
|
31470
|
+
*
|
|
31471
|
+
* Called once by BrokerProxy via `waitForInit()` (singleshot) before the first event.
|
|
31472
|
+
* Override to establish exchange connections, authenticate API clients, load configuration.
|
|
31473
|
+
*
|
|
31474
|
+
* Default implementation: Logs initialization event.
|
|
31475
|
+
*
|
|
31476
|
+
* @example
|
|
31477
|
+
* ```typescript
|
|
31478
|
+
* async waitForInit() {
|
|
31479
|
+
* super.waitForInit(); // Keep parent logging
|
|
31480
|
+
* this.exchange = new ExchangeClient(process.env.API_KEY);
|
|
31481
|
+
* await this.exchange.authenticate();
|
|
31482
|
+
* }
|
|
31483
|
+
* ```
|
|
31484
|
+
*/
|
|
31485
|
+
async waitForInit() {
|
|
31486
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_WAIT_FOR_INIT, {});
|
|
31487
|
+
}
|
|
31488
|
+
/**
|
|
31489
|
+
* Called when a new position is opened (signal activated).
|
|
31490
|
+
*
|
|
31491
|
+
* Triggered automatically via syncSubject when a scheduled signal's priceOpen is hit.
|
|
31492
|
+
* Use to place the actual entry order on the exchange.
|
|
31493
|
+
*
|
|
31494
|
+
* Default implementation: Logs signal-open event.
|
|
31495
|
+
*
|
|
31496
|
+
* @param payload - Signal open details: symbol, cost, position, priceOpen, priceTakeProfit, priceStopLoss, context, backtest
|
|
31497
|
+
*
|
|
31498
|
+
* @example
|
|
31499
|
+
* ```typescript
|
|
31500
|
+
* async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
|
|
31501
|
+
* super.onSignalOpenCommit(payload); // Keep parent logging
|
|
31502
|
+
* await this.exchange.placeMarketOrder({
|
|
31503
|
+
* symbol: payload.symbol,
|
|
31504
|
+
* side: payload.position === "long" ? "BUY" : "SELL",
|
|
31505
|
+
* quantity: payload.cost / payload.priceOpen,
|
|
31506
|
+
* });
|
|
31507
|
+
* }
|
|
31508
|
+
* ```
|
|
31509
|
+
*/
|
|
31510
|
+
async onSignalOpenCommit(payload) {
|
|
31511
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_SIGNAL_OPEN, {
|
|
31512
|
+
symbol: payload.symbol,
|
|
31513
|
+
context: payload.context,
|
|
31514
|
+
});
|
|
31515
|
+
}
|
|
31516
|
+
/**
|
|
31517
|
+
* Called when a position is fully closed (SL/TP hit or manual close).
|
|
31518
|
+
*
|
|
31519
|
+
* Triggered automatically via syncSubject when a pending signal is closed.
|
|
31520
|
+
* Use to place the exit order and record final PnL.
|
|
31521
|
+
*
|
|
31522
|
+
* Default implementation: Logs signal-close event.
|
|
31523
|
+
*
|
|
31524
|
+
* @param payload - Signal close details: symbol, cost, position, currentPrice, pnl, totalEntries, totalPartials, context, backtest
|
|
31525
|
+
*
|
|
31526
|
+
* @example
|
|
31527
|
+
* ```typescript
|
|
31528
|
+
* async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
|
|
31529
|
+
* super.onSignalCloseCommit(payload); // Keep parent logging
|
|
31530
|
+
* await this.exchange.closePosition(payload.symbol);
|
|
31531
|
+
* await this.db.recordTrade({ symbol: payload.symbol, pnl: payload.pnl });
|
|
31532
|
+
* }
|
|
31533
|
+
* ```
|
|
31534
|
+
*/
|
|
31535
|
+
async onSignalCloseCommit(payload) {
|
|
31536
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_SIGNAL_CLOSE, {
|
|
31537
|
+
symbol: payload.symbol,
|
|
31538
|
+
context: payload.context,
|
|
31539
|
+
});
|
|
31540
|
+
}
|
|
31541
|
+
/**
|
|
31542
|
+
* Called when a partial close at profit is executed.
|
|
31543
|
+
*
|
|
31544
|
+
* Triggered explicitly from strategy.ts / Live.ts / Backtest.ts after all validations pass,
|
|
31545
|
+
* before `strategyCoreService.partialProfit()`. If this method throws, the DI mutation is skipped.
|
|
31546
|
+
* Use to partially close the position on the exchange at the profit level.
|
|
31547
|
+
*
|
|
31548
|
+
* Default implementation: Logs partial profit event.
|
|
31549
|
+
*
|
|
31550
|
+
* @param payload - Partial profit details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest
|
|
31551
|
+
*
|
|
31552
|
+
* @example
|
|
31553
|
+
* ```typescript
|
|
31554
|
+
* async onPartialProfitCommit(payload: BrokerPartialProfitPayload) {
|
|
31555
|
+
* super.onPartialProfitCommit(payload); // Keep parent logging
|
|
31556
|
+
* await this.exchange.reducePosition({
|
|
31557
|
+
* symbol: payload.symbol,
|
|
31558
|
+
* dollarAmount: payload.cost,
|
|
31559
|
+
* price: payload.currentPrice,
|
|
31560
|
+
* });
|
|
31561
|
+
* }
|
|
31562
|
+
* ```
|
|
31563
|
+
*/
|
|
31564
|
+
async onPartialProfitCommit(payload) {
|
|
31565
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_PARTIAL_PROFIT, {
|
|
31566
|
+
symbol: payload.symbol,
|
|
31567
|
+
context: payload.context,
|
|
31568
|
+
});
|
|
31569
|
+
}
|
|
31570
|
+
/**
|
|
31571
|
+
* Called when a partial close at loss is executed.
|
|
31572
|
+
*
|
|
31573
|
+
* Triggered explicitly from strategy.ts / Live.ts / Backtest.ts after all validations pass,
|
|
31574
|
+
* before `strategyCoreService.partialLoss()`. If this method throws, the DI mutation is skipped.
|
|
31575
|
+
* Use to partially close the position on the exchange at the loss level.
|
|
31576
|
+
*
|
|
31577
|
+
* Default implementation: Logs partial loss event.
|
|
31578
|
+
*
|
|
31579
|
+
* @param payload - Partial loss details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest
|
|
31580
|
+
*
|
|
31581
|
+
* @example
|
|
31582
|
+
* ```typescript
|
|
31583
|
+
* async onPartialLossCommit(payload: BrokerPartialLossPayload) {
|
|
31584
|
+
* super.onPartialLossCommit(payload); // Keep parent logging
|
|
31585
|
+
* await this.exchange.reducePosition({
|
|
31586
|
+
* symbol: payload.symbol,
|
|
31587
|
+
* dollarAmount: payload.cost,
|
|
31588
|
+
* price: payload.currentPrice,
|
|
31589
|
+
* });
|
|
31590
|
+
* }
|
|
31591
|
+
* ```
|
|
31592
|
+
*/
|
|
31593
|
+
async onPartialLossCommit(payload) {
|
|
31594
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_PARTIAL_LOSS, {
|
|
31595
|
+
symbol: payload.symbol,
|
|
31596
|
+
context: payload.context,
|
|
31597
|
+
});
|
|
31598
|
+
}
|
|
31599
|
+
/**
|
|
31600
|
+
* Called when the trailing stop-loss level is updated.
|
|
31601
|
+
*
|
|
31602
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.trailingStop()`.
|
|
31603
|
+
* `newStopLossPrice` is the absolute SL price — use it to update the exchange order directly.
|
|
31604
|
+
*
|
|
31605
|
+
* Default implementation: Logs trailing stop event.
|
|
31606
|
+
*
|
|
31607
|
+
* @param payload - Trailing stop details: symbol, percentShift, currentPrice, newStopLossPrice, context, backtest
|
|
31608
|
+
*
|
|
31609
|
+
* @example
|
|
31610
|
+
* ```typescript
|
|
31611
|
+
* async onTrailingStopCommit(payload: BrokerTrailingStopPayload) {
|
|
31612
|
+
* super.onTrailingStopCommit(payload); // Keep parent logging
|
|
31613
|
+
* await this.exchange.updateStopLoss({
|
|
31614
|
+
* symbol: payload.symbol,
|
|
31615
|
+
* price: payload.newStopLossPrice,
|
|
31616
|
+
* });
|
|
31617
|
+
* }
|
|
31618
|
+
* ```
|
|
31619
|
+
*/
|
|
31620
|
+
async onTrailingStopCommit(payload) {
|
|
31621
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_TRAILING_STOP, {
|
|
31622
|
+
symbol: payload.symbol,
|
|
31623
|
+
context: payload.context,
|
|
31624
|
+
});
|
|
31625
|
+
}
|
|
31626
|
+
/**
|
|
31627
|
+
* Called when the trailing take-profit level is updated.
|
|
31628
|
+
*
|
|
31629
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.trailingTake()`.
|
|
31630
|
+
* `newTakeProfitPrice` is the absolute TP price — use it to update the exchange order directly.
|
|
31631
|
+
*
|
|
31632
|
+
* Default implementation: Logs trailing take event.
|
|
31633
|
+
*
|
|
31634
|
+
* @param payload - Trailing take details: symbol, percentShift, currentPrice, newTakeProfitPrice, context, backtest
|
|
31635
|
+
*
|
|
31636
|
+
* @example
|
|
31637
|
+
* ```typescript
|
|
31638
|
+
* async onTrailingTakeCommit(payload: BrokerTrailingTakePayload) {
|
|
31639
|
+
* super.onTrailingTakeCommit(payload); // Keep parent logging
|
|
31640
|
+
* await this.exchange.updateTakeProfit({
|
|
31641
|
+
* symbol: payload.symbol,
|
|
31642
|
+
* price: payload.newTakeProfitPrice,
|
|
31643
|
+
* });
|
|
31644
|
+
* }
|
|
31645
|
+
* ```
|
|
31646
|
+
*/
|
|
31647
|
+
async onTrailingTakeCommit(payload) {
|
|
31648
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_TRAILING_TAKE, {
|
|
31649
|
+
symbol: payload.symbol,
|
|
31650
|
+
context: payload.context,
|
|
31651
|
+
});
|
|
31652
|
+
}
|
|
31653
|
+
/**
|
|
31654
|
+
* Called when the stop-loss is moved to breakeven (entry price).
|
|
31655
|
+
*
|
|
31656
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.breakeven()`.
|
|
31657
|
+
* `newStopLossPrice` equals `effectivePriceOpen` — the position's effective entry price.
|
|
31658
|
+
* `newTakeProfitPrice` is unchanged by breakeven.
|
|
31659
|
+
*
|
|
31660
|
+
* Default implementation: Logs breakeven event.
|
|
31661
|
+
*
|
|
31662
|
+
* @param payload - Breakeven details: symbol, currentPrice, newStopLossPrice, newTakeProfitPrice, context, backtest
|
|
31663
|
+
*
|
|
31664
|
+
* @example
|
|
31665
|
+
* ```typescript
|
|
31666
|
+
* async onBreakevenCommit(payload: BrokerBreakevenPayload) {
|
|
31667
|
+
* super.onBreakevenCommit(payload); // Keep parent logging
|
|
31668
|
+
* await this.exchange.updateStopLoss({
|
|
31669
|
+
* symbol: payload.symbol,
|
|
31670
|
+
* price: payload.newStopLossPrice, // = entry price
|
|
31671
|
+
* });
|
|
31672
|
+
* }
|
|
31673
|
+
* ```
|
|
31674
|
+
*/
|
|
31675
|
+
async onBreakevenCommit(payload) {
|
|
31676
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_BREAKEVEN, {
|
|
31677
|
+
symbol: payload.symbol,
|
|
31678
|
+
context: payload.context,
|
|
31679
|
+
});
|
|
31680
|
+
}
|
|
31681
|
+
/**
|
|
31682
|
+
* Called when a new DCA entry is added to the active position.
|
|
31683
|
+
*
|
|
31684
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.averageBuy()`.
|
|
31685
|
+
* `currentPrice` is the market price at which the new averaging entry is placed.
|
|
31686
|
+
* `cost` is the dollar amount of the new DCA entry.
|
|
31687
|
+
*
|
|
31688
|
+
* Default implementation: Logs average buy event.
|
|
31689
|
+
*
|
|
31690
|
+
* @param payload - Average buy details: symbol, currentPrice, cost, context, backtest
|
|
31691
|
+
*
|
|
31692
|
+
* @example
|
|
31693
|
+
* ```typescript
|
|
31694
|
+
* async onAverageBuyCommit(payload: BrokerAverageBuyPayload) {
|
|
31695
|
+
* super.onAverageBuyCommit(payload); // Keep parent logging
|
|
31696
|
+
* await this.exchange.placeMarketOrder({
|
|
31697
|
+
* symbol: payload.symbol,
|
|
31698
|
+
* side: "BUY",
|
|
31699
|
+
* quantity: payload.cost / payload.currentPrice,
|
|
31700
|
+
* });
|
|
31701
|
+
* }
|
|
31702
|
+
* ```
|
|
31703
|
+
*/
|
|
31704
|
+
async onAverageBuyCommit(payload) {
|
|
31705
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_AVERAGE_BUY, {
|
|
31706
|
+
symbol: payload.symbol,
|
|
31707
|
+
context: payload.context,
|
|
31708
|
+
});
|
|
31709
|
+
}
|
|
31710
|
+
}
|
|
31711
|
+
// @ts-ignore
|
|
31712
|
+
BrokerBase = functoolsKit.makeExtendable(BrokerBase);
|
|
31713
|
+
/**
|
|
31714
|
+
* Global singleton instance of BrokerAdapter.
|
|
31715
|
+
* Provides static-like access to all broker commit methods and lifecycle controls.
|
|
31716
|
+
*
|
|
31717
|
+
* @example
|
|
31718
|
+
* ```typescript
|
|
31719
|
+
* import { Broker } from "backtest-kit";
|
|
31720
|
+
*
|
|
31721
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31722
|
+
* const dispose = Broker.enable();
|
|
31723
|
+
* ```
|
|
31724
|
+
*/
|
|
31725
|
+
const Broker = new BrokerAdapter();
|
|
31726
|
+
|
|
30160
31727
|
const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
|
|
30161
31728
|
const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
|
|
30162
31729
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
@@ -30165,6 +31732,8 @@ const PARTIAL_PROFIT_COST_METHOD_NAME = "strategy.commitPartialProfitCost";
|
|
|
30165
31732
|
const PARTIAL_LOSS_COST_METHOD_NAME = "strategy.commitPartialLossCost";
|
|
30166
31733
|
const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
|
|
30167
31734
|
const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
|
|
31735
|
+
const TRAILING_STOP_COST_METHOD_NAME = "strategy.commitTrailingStopCost";
|
|
31736
|
+
const TRAILING_PROFIT_COST_METHOD_NAME = "strategy.commitTrailingTakeCost";
|
|
30168
31737
|
const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
|
|
30169
31738
|
const ACTIVATE_SCHEDULED_METHOD_NAME = "strategy.commitActivateScheduled";
|
|
30170
31739
|
const AVERAGE_BUY_METHOD_NAME = "strategy.commitAverageBuy";
|
|
@@ -30294,6 +31863,28 @@ async function commitPartialProfit(symbol, percentToClose) {
|
|
|
30294
31863
|
const currentPrice = await getAveragePrice(symbol);
|
|
30295
31864
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30296
31865
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
31866
|
+
const investedCostForProfit = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
31867
|
+
if (investedCostForProfit === null) {
|
|
31868
|
+
return false;
|
|
31869
|
+
}
|
|
31870
|
+
const signalForProfit = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
31871
|
+
if (!signalForProfit) {
|
|
31872
|
+
return false;
|
|
31873
|
+
}
|
|
31874
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
31875
|
+
return false;
|
|
31876
|
+
}
|
|
31877
|
+
await Broker.commitPartialProfit({
|
|
31878
|
+
symbol,
|
|
31879
|
+
percentToClose,
|
|
31880
|
+
cost: percentToCloseCost(percentToClose, investedCostForProfit),
|
|
31881
|
+
currentPrice,
|
|
31882
|
+
position: signalForProfit.position,
|
|
31883
|
+
priceTakeProfit: signalForProfit.priceTakeProfit,
|
|
31884
|
+
priceStopLoss: signalForProfit.priceStopLoss,
|
|
31885
|
+
context: { exchangeName, frameName, strategyName },
|
|
31886
|
+
backtest: isBacktest,
|
|
31887
|
+
});
|
|
30297
31888
|
return await bt.strategyCoreService.partialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30298
31889
|
}
|
|
30299
31890
|
/**
|
|
@@ -30337,6 +31928,28 @@ async function commitPartialLoss(symbol, percentToClose) {
|
|
|
30337
31928
|
const currentPrice = await getAveragePrice(symbol);
|
|
30338
31929
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30339
31930
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
31931
|
+
const investedCostForLoss = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
31932
|
+
if (investedCostForLoss === null) {
|
|
31933
|
+
return false;
|
|
31934
|
+
}
|
|
31935
|
+
const signalForLoss = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
31936
|
+
if (!signalForLoss) {
|
|
31937
|
+
return false;
|
|
31938
|
+
}
|
|
31939
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
31940
|
+
return false;
|
|
31941
|
+
}
|
|
31942
|
+
await Broker.commitPartialLoss({
|
|
31943
|
+
symbol,
|
|
31944
|
+
percentToClose,
|
|
31945
|
+
cost: percentToCloseCost(percentToClose, investedCostForLoss),
|
|
31946
|
+
currentPrice,
|
|
31947
|
+
position: signalForLoss.position,
|
|
31948
|
+
priceTakeProfit: signalForLoss.priceTakeProfit,
|
|
31949
|
+
priceStopLoss: signalForLoss.priceStopLoss,
|
|
31950
|
+
context: { exchangeName, frameName, strategyName },
|
|
31951
|
+
backtest: isBacktest,
|
|
31952
|
+
});
|
|
30340
31953
|
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30341
31954
|
}
|
|
30342
31955
|
/**
|
|
@@ -30396,6 +32009,26 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
30396
32009
|
}
|
|
30397
32010
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30398
32011
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32012
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32013
|
+
if (!signal) {
|
|
32014
|
+
return false;
|
|
32015
|
+
}
|
|
32016
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32017
|
+
if (effectivePriceOpen === null) {
|
|
32018
|
+
return false;
|
|
32019
|
+
}
|
|
32020
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32021
|
+
return false;
|
|
32022
|
+
}
|
|
32023
|
+
await Broker.commitTrailingStop({
|
|
32024
|
+
symbol,
|
|
32025
|
+
percentShift,
|
|
32026
|
+
currentPrice,
|
|
32027
|
+
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
32028
|
+
position: signal.position,
|
|
32029
|
+
context: { exchangeName, frameName, strategyName },
|
|
32030
|
+
backtest: isBacktest,
|
|
32031
|
+
});
|
|
30399
32032
|
return await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
30400
32033
|
}
|
|
30401
32034
|
/**
|
|
@@ -30455,6 +32088,126 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
30455
32088
|
}
|
|
30456
32089
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30457
32090
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32091
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32092
|
+
if (!signal) {
|
|
32093
|
+
return false;
|
|
32094
|
+
}
|
|
32095
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32096
|
+
if (effectivePriceOpen === null) {
|
|
32097
|
+
return false;
|
|
32098
|
+
}
|
|
32099
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32100
|
+
return false;
|
|
32101
|
+
}
|
|
32102
|
+
await Broker.commitTrailingTake({
|
|
32103
|
+
symbol,
|
|
32104
|
+
percentShift,
|
|
32105
|
+
currentPrice,
|
|
32106
|
+
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
32107
|
+
position: signal.position,
|
|
32108
|
+
context: { exchangeName, frameName, strategyName },
|
|
32109
|
+
backtest: isBacktest,
|
|
32110
|
+
});
|
|
32111
|
+
return await bt.strategyCoreService.trailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
32112
|
+
}
|
|
32113
|
+
/**
|
|
32114
|
+
* Adjusts the trailing stop-loss to an absolute price level.
|
|
32115
|
+
*
|
|
32116
|
+
* Convenience wrapper around commitTrailingStop that converts an absolute
|
|
32117
|
+
* stop-loss price to a percentShift relative to the ORIGINAL SL distance.
|
|
32118
|
+
*
|
|
32119
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32120
|
+
* Automatically fetches current price via getAveragePrice.
|
|
32121
|
+
*
|
|
32122
|
+
* @param symbol - Trading pair symbol
|
|
32123
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
32124
|
+
* @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
|
|
32125
|
+
*/
|
|
32126
|
+
async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
32127
|
+
bt.loggerService.info(TRAILING_STOP_COST_METHOD_NAME, {
|
|
32128
|
+
symbol,
|
|
32129
|
+
newStopLossPrice,
|
|
32130
|
+
});
|
|
32131
|
+
if (!ExecutionContextService.hasContext()) {
|
|
32132
|
+
throw new Error("commitTrailingStopCost requires an execution context");
|
|
32133
|
+
}
|
|
32134
|
+
if (!MethodContextService.hasContext()) {
|
|
32135
|
+
throw new Error("commitTrailingStopCost requires a method context");
|
|
32136
|
+
}
|
|
32137
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
32138
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
32139
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32140
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32141
|
+
if (!signal) {
|
|
32142
|
+
return false;
|
|
32143
|
+
}
|
|
32144
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32145
|
+
if (effectivePriceOpen === null) {
|
|
32146
|
+
return false;
|
|
32147
|
+
}
|
|
32148
|
+
const percentShift = slPriceToPercentShift(newStopLossPrice, signal.priceStopLoss, effectivePriceOpen);
|
|
32149
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32150
|
+
return false;
|
|
32151
|
+
}
|
|
32152
|
+
await Broker.commitTrailingStop({
|
|
32153
|
+
symbol,
|
|
32154
|
+
percentShift,
|
|
32155
|
+
currentPrice,
|
|
32156
|
+
newStopLossPrice,
|
|
32157
|
+
position: signal.position,
|
|
32158
|
+
context: { exchangeName, frameName, strategyName },
|
|
32159
|
+
backtest: isBacktest,
|
|
32160
|
+
});
|
|
32161
|
+
return await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
32162
|
+
}
|
|
32163
|
+
/**
|
|
32164
|
+
* Adjusts the trailing take-profit to an absolute price level.
|
|
32165
|
+
*
|
|
32166
|
+
* Convenience wrapper around commitTrailingTake that converts an absolute
|
|
32167
|
+
* take-profit price to a percentShift relative to the ORIGINAL TP distance.
|
|
32168
|
+
*
|
|
32169
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32170
|
+
* Automatically fetches current price via getAveragePrice.
|
|
32171
|
+
*
|
|
32172
|
+
* @param symbol - Trading pair symbol
|
|
32173
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
32174
|
+
* @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
|
|
32175
|
+
*/
|
|
32176
|
+
async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
32177
|
+
bt.loggerService.info(TRAILING_PROFIT_COST_METHOD_NAME, {
|
|
32178
|
+
symbol,
|
|
32179
|
+
newTakeProfitPrice,
|
|
32180
|
+
});
|
|
32181
|
+
if (!ExecutionContextService.hasContext()) {
|
|
32182
|
+
throw new Error("commitTrailingTakeCost requires an execution context");
|
|
32183
|
+
}
|
|
32184
|
+
if (!MethodContextService.hasContext()) {
|
|
32185
|
+
throw new Error("commitTrailingTakeCost requires a method context");
|
|
32186
|
+
}
|
|
32187
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
32188
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
32189
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32190
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32191
|
+
if (!signal) {
|
|
32192
|
+
return false;
|
|
32193
|
+
}
|
|
32194
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32195
|
+
if (effectivePriceOpen === null) {
|
|
32196
|
+
return false;
|
|
32197
|
+
}
|
|
32198
|
+
const percentShift = tpPriceToPercentShift(newTakeProfitPrice, signal.priceTakeProfit, effectivePriceOpen);
|
|
32199
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32200
|
+
return false;
|
|
32201
|
+
}
|
|
32202
|
+
await Broker.commitTrailingTake({
|
|
32203
|
+
symbol,
|
|
32204
|
+
percentShift,
|
|
32205
|
+
currentPrice,
|
|
32206
|
+
newTakeProfitPrice,
|
|
32207
|
+
position: signal.position,
|
|
32208
|
+
context: { exchangeName, frameName, strategyName },
|
|
32209
|
+
backtest: isBacktest,
|
|
32210
|
+
});
|
|
30458
32211
|
return await bt.strategyCoreService.trailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
30459
32212
|
}
|
|
30460
32213
|
/**
|
|
@@ -30495,6 +32248,26 @@ async function commitBreakeven(symbol) {
|
|
|
30495
32248
|
const currentPrice = await getAveragePrice(symbol);
|
|
30496
32249
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30497
32250
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32251
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32252
|
+
if (!signal) {
|
|
32253
|
+
return false;
|
|
32254
|
+
}
|
|
32255
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32256
|
+
if (effectivePriceOpen === null) {
|
|
32257
|
+
return false;
|
|
32258
|
+
}
|
|
32259
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32260
|
+
return false;
|
|
32261
|
+
}
|
|
32262
|
+
await Broker.commitBreakeven({
|
|
32263
|
+
symbol,
|
|
32264
|
+
currentPrice,
|
|
32265
|
+
newStopLossPrice: breakevenNewStopLossPrice(effectivePriceOpen),
|
|
32266
|
+
newTakeProfitPrice: breakevenNewTakeProfitPrice(signal.priceTakeProfit, signal._trailingPriceTakeProfit),
|
|
32267
|
+
position: signal.position,
|
|
32268
|
+
context: { exchangeName, frameName, strategyName },
|
|
32269
|
+
backtest: isBacktest,
|
|
32270
|
+
});
|
|
30498
32271
|
return await bt.strategyCoreService.breakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
30499
32272
|
}
|
|
30500
32273
|
/**
|
|
@@ -30569,6 +32342,23 @@ async function commitAverageBuy(symbol, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_C
|
|
|
30569
32342
|
const currentPrice = await getAveragePrice(symbol);
|
|
30570
32343
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30571
32344
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32345
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateAverageBuy(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32346
|
+
return false;
|
|
32347
|
+
}
|
|
32348
|
+
const signalForAvgBuy = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32349
|
+
if (!signalForAvgBuy) {
|
|
32350
|
+
return false;
|
|
32351
|
+
}
|
|
32352
|
+
await Broker.commitAverageBuy({
|
|
32353
|
+
symbol,
|
|
32354
|
+
currentPrice,
|
|
32355
|
+
cost,
|
|
32356
|
+
position: signalForAvgBuy.position,
|
|
32357
|
+
priceTakeProfit: signalForAvgBuy.priceTakeProfit,
|
|
32358
|
+
priceStopLoss: signalForAvgBuy.priceStopLoss,
|
|
32359
|
+
context: { exchangeName, frameName, strategyName },
|
|
32360
|
+
backtest: isBacktest,
|
|
32361
|
+
});
|
|
30572
32362
|
return await bt.strategyCoreService.averageBuy(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName }, cost);
|
|
30573
32363
|
}
|
|
30574
32364
|
/**
|
|
@@ -30741,7 +32531,9 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
30741
32531
|
return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
30742
32532
|
}
|
|
30743
32533
|
async function getPositionAveragePrice(symbol) {
|
|
30744
|
-
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
32534
|
+
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
32535
|
+
symbol,
|
|
32536
|
+
});
|
|
30745
32537
|
if (!ExecutionContextService.hasContext()) {
|
|
30746
32538
|
throw new Error("getPositionAveragePrice requires an execution context");
|
|
30747
32539
|
}
|
|
@@ -30753,7 +32545,9 @@ async function getPositionAveragePrice(symbol) {
|
|
|
30753
32545
|
return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30754
32546
|
}
|
|
30755
32547
|
async function getPositionInvestedCount(symbol) {
|
|
30756
|
-
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
|
|
32548
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
|
|
32549
|
+
symbol,
|
|
32550
|
+
});
|
|
30757
32551
|
if (!ExecutionContextService.hasContext()) {
|
|
30758
32552
|
throw new Error("getPositionInvestedCount requires an execution context");
|
|
30759
32553
|
}
|
|
@@ -30765,7 +32559,9 @@ async function getPositionInvestedCount(symbol) {
|
|
|
30765
32559
|
return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30766
32560
|
}
|
|
30767
32561
|
async function getPositionInvestedCost(symbol) {
|
|
30768
|
-
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
|
|
32562
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
|
|
32563
|
+
symbol,
|
|
32564
|
+
});
|
|
30769
32565
|
if (!ExecutionContextService.hasContext()) {
|
|
30770
32566
|
throw new Error("getPositionInvestedCost requires an execution context");
|
|
30771
32567
|
}
|
|
@@ -30819,7 +32615,10 @@ async function getPositionPnlPercent(symbol) {
|
|
|
30819
32615
|
* ```
|
|
30820
32616
|
*/
|
|
30821
32617
|
async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
30822
|
-
bt.loggerService.info(PARTIAL_PROFIT_COST_METHOD_NAME, {
|
|
32618
|
+
bt.loggerService.info(PARTIAL_PROFIT_COST_METHOD_NAME, {
|
|
32619
|
+
symbol,
|
|
32620
|
+
dollarAmount,
|
|
32621
|
+
});
|
|
30823
32622
|
if (!ExecutionContextService.hasContext()) {
|
|
30824
32623
|
throw new Error("commitPartialProfitCost requires an execution context");
|
|
30825
32624
|
}
|
|
@@ -30830,9 +32629,28 @@ async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
|
30830
32629
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30831
32630
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
30832
32631
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30833
|
-
if (investedCost === null)
|
|
32632
|
+
if (investedCost === null) {
|
|
32633
|
+
return false;
|
|
32634
|
+
}
|
|
32635
|
+
const signalForProfitCost = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32636
|
+
if (!signalForProfitCost) {
|
|
30834
32637
|
return false;
|
|
32638
|
+
}
|
|
30835
32639
|
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
32640
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32641
|
+
return false;
|
|
32642
|
+
}
|
|
32643
|
+
await Broker.commitPartialProfit({
|
|
32644
|
+
symbol,
|
|
32645
|
+
percentToClose,
|
|
32646
|
+
cost: dollarAmount,
|
|
32647
|
+
currentPrice,
|
|
32648
|
+
position: signalForProfitCost.position,
|
|
32649
|
+
priceTakeProfit: signalForProfitCost.priceTakeProfit,
|
|
32650
|
+
priceStopLoss: signalForProfitCost.priceStopLoss,
|
|
32651
|
+
context: { exchangeName, frameName, strategyName },
|
|
32652
|
+
backtest: isBacktest,
|
|
32653
|
+
});
|
|
30836
32654
|
return await bt.strategyCoreService.partialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30837
32655
|
}
|
|
30838
32656
|
/**
|
|
@@ -30865,7 +32683,10 @@ async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
|
30865
32683
|
* ```
|
|
30866
32684
|
*/
|
|
30867
32685
|
async function commitPartialLossCost(symbol, dollarAmount) {
|
|
30868
|
-
bt.loggerService.info(PARTIAL_LOSS_COST_METHOD_NAME, {
|
|
32686
|
+
bt.loggerService.info(PARTIAL_LOSS_COST_METHOD_NAME, {
|
|
32687
|
+
symbol,
|
|
32688
|
+
dollarAmount,
|
|
32689
|
+
});
|
|
30869
32690
|
if (!ExecutionContextService.hasContext()) {
|
|
30870
32691
|
throw new Error("commitPartialLossCost requires an execution context");
|
|
30871
32692
|
}
|
|
@@ -30876,9 +32697,28 @@ async function commitPartialLossCost(symbol, dollarAmount) {
|
|
|
30876
32697
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30877
32698
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
30878
32699
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30879
|
-
if (investedCost === null)
|
|
32700
|
+
if (investedCost === null) {
|
|
30880
32701
|
return false;
|
|
32702
|
+
}
|
|
32703
|
+
const signalForLossCost = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32704
|
+
if (!signalForLossCost) {
|
|
32705
|
+
return false;
|
|
32706
|
+
}
|
|
30881
32707
|
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
32708
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32709
|
+
return false;
|
|
32710
|
+
}
|
|
32711
|
+
await Broker.commitPartialLoss({
|
|
32712
|
+
symbol,
|
|
32713
|
+
percentToClose,
|
|
32714
|
+
cost: dollarAmount,
|
|
32715
|
+
currentPrice,
|
|
32716
|
+
position: signalForLossCost.position,
|
|
32717
|
+
priceTakeProfit: signalForLossCost.priceTakeProfit,
|
|
32718
|
+
priceStopLoss: signalForLossCost.priceStopLoss,
|
|
32719
|
+
context: { exchangeName, frameName, strategyName },
|
|
32720
|
+
backtest: isBacktest,
|
|
32721
|
+
});
|
|
30882
32722
|
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30883
32723
|
}
|
|
30884
32724
|
async function getPositionPnlCost(symbol) {
|
|
@@ -30927,6 +32767,34 @@ async function getPositionLevels(symbol) {
|
|
|
30927
32767
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
30928
32768
|
return await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30929
32769
|
}
|
|
32770
|
+
/**
|
|
32771
|
+
* Returns the list of partial close events for the current pending signal.
|
|
32772
|
+
*
|
|
32773
|
+
* Each element represents a partial profit or loss close executed via
|
|
32774
|
+
* commitPartialProfit / commitPartialLoss (or their Cost variants).
|
|
32775
|
+
*
|
|
32776
|
+
* Returns null if no pending signal exists.
|
|
32777
|
+
* Returns an empty array if no partials were executed yet.
|
|
32778
|
+
*
|
|
32779
|
+
* Each entry contains:
|
|
32780
|
+
* - `type` — "profit" or "loss"
|
|
32781
|
+
* - `percent` — percentage of position closed at this partial
|
|
32782
|
+
* - `currentPrice` — execution price of the partial close
|
|
32783
|
+
* - `costBasisAtClose` — accounting cost basis at the moment of this partial
|
|
32784
|
+
* - `entryCountAtClose` — number of DCA entries accumulated at this partial
|
|
32785
|
+
*
|
|
32786
|
+
* @param symbol - Trading pair symbol
|
|
32787
|
+
* @returns Promise resolving to array of partial close records or null
|
|
32788
|
+
*
|
|
32789
|
+
* @example
|
|
32790
|
+
* ```typescript
|
|
32791
|
+
* import { getPositionPartials } from "backtest-kit";
|
|
32792
|
+
*
|
|
32793
|
+
* const partials = await getPositionPartials("BTCUSDT");
|
|
32794
|
+
* // No partials yet: []
|
|
32795
|
+
* // After one partial profit: [{ type: "profit", percent: 50, currentPrice: 45000, ... }]
|
|
32796
|
+
* ```
|
|
32797
|
+
*/
|
|
30930
32798
|
async function getPositionPartials(symbol) {
|
|
30931
32799
|
bt.loggerService.info(GET_POSITION_PARTIALS_METHOD_NAME, { symbol });
|
|
30932
32800
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32173,6 +34041,8 @@ const BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST = "BacktestUtils.commitPartialPro
|
|
|
32173
34041
|
const BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST = "BacktestUtils.commitPartialLossCost";
|
|
32174
34042
|
const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
|
|
32175
34043
|
const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
|
|
34044
|
+
const BACKTEST_METHOD_NAME_TRAILING_STOP_COST = "BacktestUtils.commitTrailingStopCost";
|
|
34045
|
+
const BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST = "BacktestUtils.commitTrailingTakeCost";
|
|
32176
34046
|
const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
|
|
32177
34047
|
const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
|
|
32178
34048
|
const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
|
|
@@ -32352,12 +34222,13 @@ class BacktestInstance {
|
|
|
32352
34222
|
}
|
|
32353
34223
|
{
|
|
32354
34224
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
32355
|
-
riskName &&
|
|
32356
|
-
|
|
32357
|
-
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
34225
|
+
riskName &&
|
|
34226
|
+
bt.riskGlobalService.clear({
|
|
34227
|
+
riskName,
|
|
34228
|
+
exchangeName: context.exchangeName,
|
|
34229
|
+
frameName: context.frameName,
|
|
34230
|
+
backtest: true,
|
|
34231
|
+
});
|
|
32361
34232
|
riskList &&
|
|
32362
34233
|
riskList.forEach((riskName) => bt.riskGlobalService.clear({
|
|
32363
34234
|
riskName,
|
|
@@ -32707,6 +34578,16 @@ class BacktestUtils {
|
|
|
32707
34578
|
}
|
|
32708
34579
|
return await bt.strategyCoreService.getBreakeven(true, symbol, currentPrice, context);
|
|
32709
34580
|
};
|
|
34581
|
+
/**
|
|
34582
|
+
* Returns the effective (weighted average) entry price for the current pending signal.
|
|
34583
|
+
*
|
|
34584
|
+
* Accounts for all DCA entries via commitAverageBuy.
|
|
34585
|
+
* Returns null if no pending signal exists.
|
|
34586
|
+
*
|
|
34587
|
+
* @param symbol - Trading pair symbol
|
|
34588
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34589
|
+
* @returns Effective entry price, or null if no active position
|
|
34590
|
+
*/
|
|
32710
34591
|
this.getPositionAveragePrice = async (symbol, context) => {
|
|
32711
34592
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
32712
34593
|
symbol,
|
|
@@ -32725,6 +34606,15 @@ class BacktestUtils {
|
|
|
32725
34606
|
}
|
|
32726
34607
|
return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
32727
34608
|
};
|
|
34609
|
+
/**
|
|
34610
|
+
* Returns the total number of base-asset units currently held in the position.
|
|
34611
|
+
*
|
|
34612
|
+
* Includes units from all DCA entries. Returns null if no pending signal exists.
|
|
34613
|
+
*
|
|
34614
|
+
* @param symbol - Trading pair symbol
|
|
34615
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34616
|
+
* @returns Total units held, or null if no active position
|
|
34617
|
+
*/
|
|
32728
34618
|
this.getPositionInvestedCount = async (symbol, context) => {
|
|
32729
34619
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
32730
34620
|
symbol,
|
|
@@ -32743,6 +34633,15 @@ class BacktestUtils {
|
|
|
32743
34633
|
}
|
|
32744
34634
|
return await bt.strategyCoreService.getPositionInvestedCount(true, symbol, context);
|
|
32745
34635
|
};
|
|
34636
|
+
/**
|
|
34637
|
+
* Returns the total dollar cost invested in the current position.
|
|
34638
|
+
*
|
|
34639
|
+
* Sum of all entry costs across DCA entries. Returns null if no pending signal exists.
|
|
34640
|
+
*
|
|
34641
|
+
* @param symbol - Trading pair symbol
|
|
34642
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34643
|
+
* @returns Total invested cost in quote currency, or null if no active position
|
|
34644
|
+
*/
|
|
32746
34645
|
this.getPositionInvestedCost = async (symbol, context) => {
|
|
32747
34646
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
32748
34647
|
symbol,
|
|
@@ -32761,6 +34660,17 @@ class BacktestUtils {
|
|
|
32761
34660
|
}
|
|
32762
34661
|
return await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
32763
34662
|
};
|
|
34663
|
+
/**
|
|
34664
|
+
* Returns the current unrealized PnL as a percentage of the invested cost.
|
|
34665
|
+
*
|
|
34666
|
+
* Calculated relative to the effective (weighted average) entry price.
|
|
34667
|
+
* Positive for profit, negative for loss. Returns null if no pending signal exists.
|
|
34668
|
+
*
|
|
34669
|
+
* @param symbol - Trading pair symbol
|
|
34670
|
+
* @param currentPrice - Current market price
|
|
34671
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34672
|
+
* @returns PnL percentage, or null if no active position
|
|
34673
|
+
*/
|
|
32764
34674
|
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
32765
34675
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
32766
34676
|
symbol,
|
|
@@ -32780,6 +34690,17 @@ class BacktestUtils {
|
|
|
32780
34690
|
}
|
|
32781
34691
|
return await bt.strategyCoreService.getPositionPnlPercent(true, symbol, currentPrice, context);
|
|
32782
34692
|
};
|
|
34693
|
+
/**
|
|
34694
|
+
* Returns the current unrealized PnL in quote currency (dollar amount).
|
|
34695
|
+
*
|
|
34696
|
+
* Calculated as (currentPrice - effectiveEntry) * units for LONG,
|
|
34697
|
+
* reversed for SHORT. Returns null if no pending signal exists.
|
|
34698
|
+
*
|
|
34699
|
+
* @param symbol - Trading pair symbol
|
|
34700
|
+
* @param currentPrice - Current market price
|
|
34701
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34702
|
+
* @returns PnL in quote currency, or null if no active position
|
|
34703
|
+
*/
|
|
32783
34704
|
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
32784
34705
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
32785
34706
|
symbol,
|
|
@@ -32799,6 +34720,18 @@ class BacktestUtils {
|
|
|
32799
34720
|
}
|
|
32800
34721
|
return await bt.strategyCoreService.getPositionPnlCost(true, symbol, currentPrice, context);
|
|
32801
34722
|
};
|
|
34723
|
+
/**
|
|
34724
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
34725
|
+
*
|
|
34726
|
+
* The first element is always the original priceOpen (initial entry).
|
|
34727
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
34728
|
+
* Returns null if no pending signal exists.
|
|
34729
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
34730
|
+
*
|
|
34731
|
+
* @param symbol - Trading pair symbol
|
|
34732
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34733
|
+
* @returns Array of entry prices, or null if no active position
|
|
34734
|
+
*/
|
|
32802
34735
|
this.getPositionLevels = async (symbol, context) => {
|
|
32803
34736
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
32804
34737
|
symbol,
|
|
@@ -32817,6 +34750,25 @@ class BacktestUtils {
|
|
|
32817
34750
|
}
|
|
32818
34751
|
return await bt.strategyCoreService.getPositionLevels(true, symbol, context);
|
|
32819
34752
|
};
|
|
34753
|
+
/**
|
|
34754
|
+
* Returns the list of partial close events for the current pending signal.
|
|
34755
|
+
*
|
|
34756
|
+
* Each element represents a partial profit or loss close executed via
|
|
34757
|
+
* commitPartialProfit / commitPartialLoss (or their Cost variants).
|
|
34758
|
+
* Returns null if no pending signal exists.
|
|
34759
|
+
* Returns an empty array if no partials were executed yet.
|
|
34760
|
+
*
|
|
34761
|
+
* Each entry contains:
|
|
34762
|
+
* - `type` — "profit" or "loss"
|
|
34763
|
+
* - `percent` — percentage of position closed at this partial
|
|
34764
|
+
* - `currentPrice` — execution price of the partial close
|
|
34765
|
+
* - `costBasisAtClose` — accounting cost basis at the moment of this partial
|
|
34766
|
+
* - `entryCountAtClose` — number of DCA entries accumulated at this partial
|
|
34767
|
+
*
|
|
34768
|
+
* @param symbol - Trading pair symbol
|
|
34769
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34770
|
+
* @returns Array of partial close records, or null if no active position
|
|
34771
|
+
*/
|
|
32820
34772
|
this.getPositionPartials = async (symbol, context) => {
|
|
32821
34773
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
32822
34774
|
symbol,
|
|
@@ -33005,6 +34957,28 @@ class BacktestUtils {
|
|
|
33005
34957
|
actions &&
|
|
33006
34958
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT));
|
|
33007
34959
|
}
|
|
34960
|
+
const investedCostForProfit = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
34961
|
+
if (investedCostForProfit === null) {
|
|
34962
|
+
return false;
|
|
34963
|
+
}
|
|
34964
|
+
const signalForProfit = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
34965
|
+
if (!signalForProfit) {
|
|
34966
|
+
return false;
|
|
34967
|
+
}
|
|
34968
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialProfit(true, symbol, percentToClose, currentPrice, context))) {
|
|
34969
|
+
return false;
|
|
34970
|
+
}
|
|
34971
|
+
await Broker.commitPartialProfit({
|
|
34972
|
+
symbol,
|
|
34973
|
+
percentToClose,
|
|
34974
|
+
cost: percentToCloseCost(percentToClose, investedCostForProfit),
|
|
34975
|
+
currentPrice,
|
|
34976
|
+
position: signalForProfit.position,
|
|
34977
|
+
priceTakeProfit: signalForProfit.priceTakeProfit,
|
|
34978
|
+
priceStopLoss: signalForProfit.priceStopLoss,
|
|
34979
|
+
context,
|
|
34980
|
+
backtest: true,
|
|
34981
|
+
});
|
|
33008
34982
|
return await bt.strategyCoreService.partialProfit(true, symbol, percentToClose, currentPrice, context);
|
|
33009
34983
|
};
|
|
33010
34984
|
/**
|
|
@@ -33054,6 +35028,28 @@ class BacktestUtils {
|
|
|
33054
35028
|
actions &&
|
|
33055
35029
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_LOSS));
|
|
33056
35030
|
}
|
|
35031
|
+
const investedCostForLoss = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
35032
|
+
if (investedCostForLoss === null) {
|
|
35033
|
+
return false;
|
|
35034
|
+
}
|
|
35035
|
+
const signalForLoss = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35036
|
+
if (!signalForLoss) {
|
|
35037
|
+
return false;
|
|
35038
|
+
}
|
|
35039
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialLoss(true, symbol, percentToClose, currentPrice, context))) {
|
|
35040
|
+
return false;
|
|
35041
|
+
}
|
|
35042
|
+
await Broker.commitPartialLoss({
|
|
35043
|
+
symbol,
|
|
35044
|
+
percentToClose,
|
|
35045
|
+
cost: percentToCloseCost(percentToClose, investedCostForLoss),
|
|
35046
|
+
currentPrice,
|
|
35047
|
+
position: signalForLoss.position,
|
|
35048
|
+
priceTakeProfit: signalForLoss.priceTakeProfit,
|
|
35049
|
+
priceStopLoss: signalForLoss.priceStopLoss,
|
|
35050
|
+
context,
|
|
35051
|
+
backtest: true,
|
|
35052
|
+
});
|
|
33057
35053
|
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
33058
35054
|
};
|
|
33059
35055
|
/**
|
|
@@ -33105,9 +35101,28 @@ class BacktestUtils {
|
|
|
33105
35101
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
33106
35102
|
}
|
|
33107
35103
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
33108
|
-
if (investedCost === null)
|
|
35104
|
+
if (investedCost === null) {
|
|
35105
|
+
return false;
|
|
35106
|
+
}
|
|
35107
|
+
const signalForProfitCost = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35108
|
+
if (!signalForProfitCost) {
|
|
33109
35109
|
return false;
|
|
35110
|
+
}
|
|
33110
35111
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
35112
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialProfit(true, symbol, percentToClose, currentPrice, context))) {
|
|
35113
|
+
return false;
|
|
35114
|
+
}
|
|
35115
|
+
await Broker.commitPartialProfit({
|
|
35116
|
+
symbol,
|
|
35117
|
+
percentToClose,
|
|
35118
|
+
cost: dollarAmount,
|
|
35119
|
+
currentPrice,
|
|
35120
|
+
position: signalForProfitCost.position,
|
|
35121
|
+
priceTakeProfit: signalForProfitCost.priceTakeProfit,
|
|
35122
|
+
priceStopLoss: signalForProfitCost.priceStopLoss,
|
|
35123
|
+
context,
|
|
35124
|
+
backtest: true,
|
|
35125
|
+
});
|
|
33111
35126
|
return await bt.strategyCoreService.partialProfit(true, symbol, percentToClose, currentPrice, context);
|
|
33112
35127
|
};
|
|
33113
35128
|
/**
|
|
@@ -33159,9 +35174,28 @@ class BacktestUtils {
|
|
|
33159
35174
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
33160
35175
|
}
|
|
33161
35176
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
33162
|
-
if (investedCost === null)
|
|
35177
|
+
if (investedCost === null) {
|
|
35178
|
+
return false;
|
|
35179
|
+
}
|
|
35180
|
+
const signalForLossCost = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35181
|
+
if (!signalForLossCost) {
|
|
33163
35182
|
return false;
|
|
35183
|
+
}
|
|
33164
35184
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
35185
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialLoss(true, symbol, percentToClose, currentPrice, context))) {
|
|
35186
|
+
return false;
|
|
35187
|
+
}
|
|
35188
|
+
await Broker.commitPartialLoss({
|
|
35189
|
+
symbol,
|
|
35190
|
+
percentToClose,
|
|
35191
|
+
cost: dollarAmount,
|
|
35192
|
+
currentPrice,
|
|
35193
|
+
position: signalForLossCost.position,
|
|
35194
|
+
priceTakeProfit: signalForLossCost.priceTakeProfit,
|
|
35195
|
+
priceStopLoss: signalForLossCost.priceStopLoss,
|
|
35196
|
+
context,
|
|
35197
|
+
backtest: true,
|
|
35198
|
+
});
|
|
33165
35199
|
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
33166
35200
|
};
|
|
33167
35201
|
/**
|
|
@@ -33226,6 +35260,26 @@ class BacktestUtils {
|
|
|
33226
35260
|
actions &&
|
|
33227
35261
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_STOP));
|
|
33228
35262
|
}
|
|
35263
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35264
|
+
if (!signal) {
|
|
35265
|
+
return false;
|
|
35266
|
+
}
|
|
35267
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35268
|
+
if (effectivePriceOpen === null) {
|
|
35269
|
+
return false;
|
|
35270
|
+
}
|
|
35271
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingStop(true, symbol, percentShift, currentPrice, context))) {
|
|
35272
|
+
return false;
|
|
35273
|
+
}
|
|
35274
|
+
await Broker.commitTrailingStop({
|
|
35275
|
+
symbol,
|
|
35276
|
+
percentShift,
|
|
35277
|
+
currentPrice,
|
|
35278
|
+
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
35279
|
+
position: signal.position,
|
|
35280
|
+
context,
|
|
35281
|
+
backtest: true,
|
|
35282
|
+
});
|
|
33229
35283
|
return await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
|
|
33230
35284
|
};
|
|
33231
35285
|
/**
|
|
@@ -33290,6 +35344,132 @@ class BacktestUtils {
|
|
|
33290
35344
|
actions &&
|
|
33291
35345
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_PROFIT));
|
|
33292
35346
|
}
|
|
35347
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35348
|
+
if (!signal) {
|
|
35349
|
+
return false;
|
|
35350
|
+
}
|
|
35351
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35352
|
+
if (effectivePriceOpen === null) {
|
|
35353
|
+
return false;
|
|
35354
|
+
}
|
|
35355
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingTake(true, symbol, percentShift, currentPrice, context))) {
|
|
35356
|
+
return false;
|
|
35357
|
+
}
|
|
35358
|
+
await Broker.commitTrailingTake({
|
|
35359
|
+
symbol,
|
|
35360
|
+
percentShift,
|
|
35361
|
+
currentPrice,
|
|
35362
|
+
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
35363
|
+
position: signal.position,
|
|
35364
|
+
context,
|
|
35365
|
+
backtest: true,
|
|
35366
|
+
});
|
|
35367
|
+
return await bt.strategyCoreService.trailingTake(true, symbol, percentShift, currentPrice, context);
|
|
35368
|
+
};
|
|
35369
|
+
/**
|
|
35370
|
+
* Adjusts the trailing stop-loss to an absolute price level.
|
|
35371
|
+
*
|
|
35372
|
+
* Convenience wrapper around commitTrailingStop that converts an absolute
|
|
35373
|
+
* stop-loss price to a percentShift relative to the ORIGINAL SL distance.
|
|
35374
|
+
*
|
|
35375
|
+
* @param symbol - Trading pair symbol
|
|
35376
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
35377
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
35378
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35379
|
+
* @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
|
|
35380
|
+
*/
|
|
35381
|
+
this.commitTrailingStopCost = async (symbol, newStopLossPrice, currentPrice, context) => {
|
|
35382
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_STOP_COST, {
|
|
35383
|
+
symbol,
|
|
35384
|
+
newStopLossPrice,
|
|
35385
|
+
currentPrice,
|
|
35386
|
+
context,
|
|
35387
|
+
});
|
|
35388
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST);
|
|
35389
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST);
|
|
35390
|
+
{
|
|
35391
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35392
|
+
riskName &&
|
|
35393
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST);
|
|
35394
|
+
riskList &&
|
|
35395
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST));
|
|
35396
|
+
actions &&
|
|
35397
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST));
|
|
35398
|
+
}
|
|
35399
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35400
|
+
if (!signal) {
|
|
35401
|
+
return false;
|
|
35402
|
+
}
|
|
35403
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35404
|
+
if (effectivePriceOpen === null) {
|
|
35405
|
+
return false;
|
|
35406
|
+
}
|
|
35407
|
+
const percentShift = slPriceToPercentShift(newStopLossPrice, signal.priceStopLoss, effectivePriceOpen);
|
|
35408
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingStop(true, symbol, percentShift, currentPrice, context))) {
|
|
35409
|
+
return false;
|
|
35410
|
+
}
|
|
35411
|
+
await Broker.commitTrailingStop({
|
|
35412
|
+
symbol,
|
|
35413
|
+
percentShift,
|
|
35414
|
+
currentPrice,
|
|
35415
|
+
newStopLossPrice,
|
|
35416
|
+
position: signal.position,
|
|
35417
|
+
context,
|
|
35418
|
+
backtest: true,
|
|
35419
|
+
});
|
|
35420
|
+
return await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
|
|
35421
|
+
};
|
|
35422
|
+
/**
|
|
35423
|
+
* Adjusts the trailing take-profit to an absolute price level.
|
|
35424
|
+
*
|
|
35425
|
+
* Convenience wrapper around commitTrailingTake that converts an absolute
|
|
35426
|
+
* take-profit price to a percentShift relative to the ORIGINAL TP distance.
|
|
35427
|
+
*
|
|
35428
|
+
* @param symbol - Trading pair symbol
|
|
35429
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
35430
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
35431
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35432
|
+
* @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
|
|
35433
|
+
*/
|
|
35434
|
+
this.commitTrailingTakeCost = async (symbol, newTakeProfitPrice, currentPrice, context) => {
|
|
35435
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST, {
|
|
35436
|
+
symbol,
|
|
35437
|
+
newTakeProfitPrice,
|
|
35438
|
+
currentPrice,
|
|
35439
|
+
context,
|
|
35440
|
+
});
|
|
35441
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
35442
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
35443
|
+
{
|
|
35444
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35445
|
+
riskName &&
|
|
35446
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
35447
|
+
riskList &&
|
|
35448
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
35449
|
+
actions &&
|
|
35450
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
35451
|
+
}
|
|
35452
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35453
|
+
if (!signal) {
|
|
35454
|
+
return false;
|
|
35455
|
+
}
|
|
35456
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35457
|
+
if (effectivePriceOpen === null) {
|
|
35458
|
+
return false;
|
|
35459
|
+
}
|
|
35460
|
+
const percentShift = tpPriceToPercentShift(newTakeProfitPrice, signal.priceTakeProfit, effectivePriceOpen);
|
|
35461
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingTake(true, symbol, percentShift, currentPrice, context))) {
|
|
35462
|
+
return false;
|
|
35463
|
+
}
|
|
35464
|
+
await Broker.commitTrailingTake({
|
|
35465
|
+
symbol,
|
|
35466
|
+
percentShift,
|
|
35467
|
+
currentPrice,
|
|
35468
|
+
newTakeProfitPrice,
|
|
35469
|
+
position: signal.position,
|
|
35470
|
+
context,
|
|
35471
|
+
backtest: true,
|
|
35472
|
+
});
|
|
33293
35473
|
return await bt.strategyCoreService.trailingTake(true, symbol, percentShift, currentPrice, context);
|
|
33294
35474
|
};
|
|
33295
35475
|
/**
|
|
@@ -33330,6 +35510,26 @@ class BacktestUtils {
|
|
|
33330
35510
|
actions &&
|
|
33331
35511
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_BREAKEVEN));
|
|
33332
35512
|
}
|
|
35513
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35514
|
+
if (!signal) {
|
|
35515
|
+
return false;
|
|
35516
|
+
}
|
|
35517
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35518
|
+
if (effectivePriceOpen === null) {
|
|
35519
|
+
return false;
|
|
35520
|
+
}
|
|
35521
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateBreakeven(true, symbol, currentPrice, context))) {
|
|
35522
|
+
return false;
|
|
35523
|
+
}
|
|
35524
|
+
await Broker.commitBreakeven({
|
|
35525
|
+
symbol,
|
|
35526
|
+
currentPrice,
|
|
35527
|
+
newStopLossPrice: breakevenNewStopLossPrice(effectivePriceOpen),
|
|
35528
|
+
newTakeProfitPrice: breakevenNewTakeProfitPrice(signal.priceTakeProfit, signal._trailingPriceTakeProfit),
|
|
35529
|
+
position: signal.position,
|
|
35530
|
+
context,
|
|
35531
|
+
backtest: true,
|
|
35532
|
+
});
|
|
33333
35533
|
return await bt.strategyCoreService.breakeven(true, symbol, currentPrice, context);
|
|
33334
35534
|
};
|
|
33335
35535
|
/**
|
|
@@ -33413,6 +35613,23 @@ class BacktestUtils {
|
|
|
33413
35613
|
actions &&
|
|
33414
35614
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_AVERAGE_BUY));
|
|
33415
35615
|
}
|
|
35616
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateAverageBuy(true, symbol, currentPrice, context))) {
|
|
35617
|
+
return false;
|
|
35618
|
+
}
|
|
35619
|
+
const signalForAvgBuy = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35620
|
+
if (!signalForAvgBuy) {
|
|
35621
|
+
return false;
|
|
35622
|
+
}
|
|
35623
|
+
await Broker.commitAverageBuy({
|
|
35624
|
+
symbol,
|
|
35625
|
+
currentPrice,
|
|
35626
|
+
cost,
|
|
35627
|
+
position: signalForAvgBuy.position,
|
|
35628
|
+
priceTakeProfit: signalForAvgBuy.priceTakeProfit,
|
|
35629
|
+
priceStopLoss: signalForAvgBuy.priceStopLoss,
|
|
35630
|
+
context,
|
|
35631
|
+
backtest: true,
|
|
35632
|
+
});
|
|
33416
35633
|
return await bt.strategyCoreService.averageBuy(true, symbol, currentPrice, context, cost);
|
|
33417
35634
|
};
|
|
33418
35635
|
/**
|
|
@@ -33601,6 +35818,8 @@ const LIVE_METHOD_NAME_PARTIAL_PROFIT_COST = "LiveUtils.commitPartialProfitCost"
|
|
|
33601
35818
|
const LIVE_METHOD_NAME_PARTIAL_LOSS_COST = "LiveUtils.commitPartialLossCost";
|
|
33602
35819
|
const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
|
|
33603
35820
|
const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
|
|
35821
|
+
const LIVE_METHOD_NAME_TRAILING_STOP_COST = "LiveUtils.commitTrailingStopCost";
|
|
35822
|
+
const LIVE_METHOD_NAME_TRAILING_PROFIT_COST = "LiveUtils.commitTrailingTakeCost";
|
|
33604
35823
|
const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
|
|
33605
35824
|
const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
|
|
33606
35825
|
/**
|
|
@@ -33727,43 +35946,82 @@ class LiveInstance {
|
|
|
33727
35946
|
context,
|
|
33728
35947
|
});
|
|
33729
35948
|
{
|
|
33730
|
-
bt.backtestMarkdownService.clear({
|
|
33731
|
-
bt.liveMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33732
|
-
bt.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33733
|
-
bt.performanceMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33734
|
-
bt.partialMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33735
|
-
bt.riskMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33736
|
-
}
|
|
33737
|
-
{
|
|
33738
|
-
bt.strategyCoreService.clear({
|
|
35949
|
+
bt.backtestMarkdownService.clear({
|
|
33739
35950
|
symbol,
|
|
33740
35951
|
strategyName: context.strategyName,
|
|
33741
35952
|
exchangeName: context.exchangeName,
|
|
33742
35953
|
frameName: "",
|
|
33743
35954
|
backtest: false,
|
|
33744
35955
|
});
|
|
33745
|
-
|
|
33746
|
-
|
|
33747
|
-
|
|
33748
|
-
riskName && bt.riskGlobalService.clear({
|
|
33749
|
-
riskName,
|
|
35956
|
+
bt.liveMarkdownService.clear({
|
|
35957
|
+
symbol,
|
|
35958
|
+
strategyName: context.strategyName,
|
|
33750
35959
|
exchangeName: context.exchangeName,
|
|
33751
35960
|
frameName: "",
|
|
33752
|
-
backtest: false
|
|
35961
|
+
backtest: false,
|
|
33753
35962
|
});
|
|
33754
|
-
|
|
33755
|
-
|
|
35963
|
+
bt.scheduleMarkdownService.clear({
|
|
35964
|
+
symbol,
|
|
35965
|
+
strategyName: context.strategyName,
|
|
35966
|
+
exchangeName: context.exchangeName,
|
|
35967
|
+
frameName: "",
|
|
35968
|
+
backtest: false,
|
|
35969
|
+
});
|
|
35970
|
+
bt.performanceMarkdownService.clear({
|
|
35971
|
+
symbol,
|
|
35972
|
+
strategyName: context.strategyName,
|
|
33756
35973
|
exchangeName: context.exchangeName,
|
|
33757
35974
|
frameName: "",
|
|
33758
|
-
backtest: false
|
|
33759
|
-
})
|
|
33760
|
-
|
|
33761
|
-
|
|
35975
|
+
backtest: false,
|
|
35976
|
+
});
|
|
35977
|
+
bt.partialMarkdownService.clear({
|
|
35978
|
+
symbol,
|
|
35979
|
+
strategyName: context.strategyName,
|
|
35980
|
+
exchangeName: context.exchangeName,
|
|
35981
|
+
frameName: "",
|
|
35982
|
+
backtest: false,
|
|
35983
|
+
});
|
|
35984
|
+
bt.riskMarkdownService.clear({
|
|
35985
|
+
symbol,
|
|
33762
35986
|
strategyName: context.strategyName,
|
|
33763
35987
|
exchangeName: context.exchangeName,
|
|
33764
35988
|
frameName: "",
|
|
33765
|
-
backtest: false
|
|
33766
|
-
})
|
|
35989
|
+
backtest: false,
|
|
35990
|
+
});
|
|
35991
|
+
}
|
|
35992
|
+
{
|
|
35993
|
+
bt.strategyCoreService.clear({
|
|
35994
|
+
symbol,
|
|
35995
|
+
strategyName: context.strategyName,
|
|
35996
|
+
exchangeName: context.exchangeName,
|
|
35997
|
+
frameName: "",
|
|
35998
|
+
backtest: false,
|
|
35999
|
+
});
|
|
36000
|
+
}
|
|
36001
|
+
{
|
|
36002
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
36003
|
+
riskName &&
|
|
36004
|
+
bt.riskGlobalService.clear({
|
|
36005
|
+
riskName,
|
|
36006
|
+
exchangeName: context.exchangeName,
|
|
36007
|
+
frameName: "",
|
|
36008
|
+
backtest: false,
|
|
36009
|
+
});
|
|
36010
|
+
riskList &&
|
|
36011
|
+
riskList.forEach((riskName) => bt.riskGlobalService.clear({
|
|
36012
|
+
riskName,
|
|
36013
|
+
exchangeName: context.exchangeName,
|
|
36014
|
+
frameName: "",
|
|
36015
|
+
backtest: false,
|
|
36016
|
+
}));
|
|
36017
|
+
actions &&
|
|
36018
|
+
actions.forEach((actionName) => bt.actionCoreService.clear({
|
|
36019
|
+
actionName,
|
|
36020
|
+
strategyName: context.strategyName,
|
|
36021
|
+
exchangeName: context.exchangeName,
|
|
36022
|
+
frameName: "",
|
|
36023
|
+
backtest: false,
|
|
36024
|
+
}));
|
|
33767
36025
|
}
|
|
33768
36026
|
return bt.liveCommandService.run(symbol, context);
|
|
33769
36027
|
};
|
|
@@ -33806,7 +36064,7 @@ class LiveInstance {
|
|
|
33806
36064
|
bt.strategyCoreService.stopStrategy(false, symbol, {
|
|
33807
36065
|
strategyName: context.strategyName,
|
|
33808
36066
|
exchangeName: context.exchangeName,
|
|
33809
|
-
frameName: ""
|
|
36067
|
+
frameName: "",
|
|
33810
36068
|
});
|
|
33811
36069
|
bt.strategyCoreService
|
|
33812
36070
|
.hasPendingSignal(false, symbol, {
|
|
@@ -33887,9 +36145,12 @@ class LiveUtils {
|
|
|
33887
36145
|
}
|
|
33888
36146
|
{
|
|
33889
36147
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33890
|
-
riskName &&
|
|
33891
|
-
|
|
33892
|
-
|
|
36148
|
+
riskName &&
|
|
36149
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_RUN);
|
|
36150
|
+
riskList &&
|
|
36151
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_RUN));
|
|
36152
|
+
actions &&
|
|
36153
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_RUN));
|
|
33893
36154
|
}
|
|
33894
36155
|
const instance = this._getInstance(symbol, context.strategyName, context.exchangeName);
|
|
33895
36156
|
return instance.run(symbol, context);
|
|
@@ -33920,9 +36181,12 @@ class LiveUtils {
|
|
|
33920
36181
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_BACKGROUND);
|
|
33921
36182
|
{
|
|
33922
36183
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33923
|
-
riskName &&
|
|
33924
|
-
|
|
33925
|
-
|
|
36184
|
+
riskName &&
|
|
36185
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BACKGROUND);
|
|
36186
|
+
riskList &&
|
|
36187
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BACKGROUND));
|
|
36188
|
+
actions &&
|
|
36189
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_BACKGROUND));
|
|
33926
36190
|
}
|
|
33927
36191
|
const instance = this._getInstance(symbol, context.strategyName, context.exchangeName);
|
|
33928
36192
|
return instance.background(symbol, context);
|
|
@@ -33952,9 +36216,12 @@ class LiveUtils {
|
|
|
33952
36216
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
33953
36217
|
{
|
|
33954
36218
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33955
|
-
riskName &&
|
|
33956
|
-
|
|
33957
|
-
|
|
36219
|
+
riskName &&
|
|
36220
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
36221
|
+
riskList &&
|
|
36222
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL));
|
|
36223
|
+
actions &&
|
|
36224
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL));
|
|
33958
36225
|
}
|
|
33959
36226
|
return await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
33960
36227
|
strategyName: context.strategyName,
|
|
@@ -33986,9 +36253,12 @@ class LiveUtils {
|
|
|
33986
36253
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED);
|
|
33987
36254
|
{
|
|
33988
36255
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33989
|
-
riskName &&
|
|
33990
|
-
|
|
33991
|
-
|
|
36256
|
+
riskName &&
|
|
36257
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED);
|
|
36258
|
+
riskList &&
|
|
36259
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED));
|
|
36260
|
+
actions &&
|
|
36261
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED));
|
|
33992
36262
|
}
|
|
33993
36263
|
return await bt.strategyCoreService.getTotalPercentClosed(false, symbol, {
|
|
33994
36264
|
strategyName: context.strategyName,
|
|
@@ -34019,9 +36289,12 @@ class LiveUtils {
|
|
|
34019
36289
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED);
|
|
34020
36290
|
{
|
|
34021
36291
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34022
|
-
riskName &&
|
|
34023
|
-
|
|
34024
|
-
|
|
36292
|
+
riskName &&
|
|
36293
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED);
|
|
36294
|
+
riskList &&
|
|
36295
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED));
|
|
36296
|
+
actions &&
|
|
36297
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED));
|
|
34025
36298
|
}
|
|
34026
36299
|
return await bt.strategyCoreService.getTotalCostClosed(false, symbol, {
|
|
34027
36300
|
strategyName: context.strategyName,
|
|
@@ -34054,9 +36327,12 @@ class LiveUtils {
|
|
|
34054
36327
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
34055
36328
|
{
|
|
34056
36329
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34057
|
-
riskName &&
|
|
34058
|
-
|
|
34059
|
-
|
|
36330
|
+
riskName &&
|
|
36331
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
36332
|
+
riskList &&
|
|
36333
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL));
|
|
36334
|
+
actions &&
|
|
36335
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL));
|
|
34060
36336
|
}
|
|
34061
36337
|
return await bt.strategyCoreService.getScheduledSignal(false, symbol, currentPrice, {
|
|
34062
36338
|
strategyName: context.strategyName,
|
|
@@ -34097,9 +36373,12 @@ class LiveUtils {
|
|
|
34097
36373
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_BREAKEVEN);
|
|
34098
36374
|
{
|
|
34099
36375
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34100
|
-
riskName &&
|
|
34101
|
-
|
|
34102
|
-
|
|
36376
|
+
riskName &&
|
|
36377
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN);
|
|
36378
|
+
riskList &&
|
|
36379
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN));
|
|
36380
|
+
actions &&
|
|
36381
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_BREAKEVEN));
|
|
34103
36382
|
}
|
|
34104
36383
|
return await bt.strategyCoreService.getBreakeven(false, symbol, currentPrice, {
|
|
34105
36384
|
strategyName: context.strategyName,
|
|
@@ -34107,15 +36386,31 @@ class LiveUtils {
|
|
|
34107
36386
|
frameName: "",
|
|
34108
36387
|
});
|
|
34109
36388
|
};
|
|
36389
|
+
/**
|
|
36390
|
+
* Returns the effective (weighted average) entry price for the current pending signal.
|
|
36391
|
+
*
|
|
36392
|
+
* Accounts for all DCA entries via commitAverageBuy.
|
|
36393
|
+
* Returns null if no pending signal exists.
|
|
36394
|
+
*
|
|
36395
|
+
* @param symbol - Trading pair symbol
|
|
36396
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36397
|
+
* @returns Effective entry price, or null if no active position
|
|
36398
|
+
*/
|
|
34110
36399
|
this.getPositionAveragePrice = async (symbol, context) => {
|
|
34111
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
36400
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
36401
|
+
symbol,
|
|
36402
|
+
context,
|
|
36403
|
+
});
|
|
34112
36404
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
34113
36405
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
34114
36406
|
{
|
|
34115
36407
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34116
|
-
riskName &&
|
|
34117
|
-
|
|
34118
|
-
|
|
36408
|
+
riskName &&
|
|
36409
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
36410
|
+
riskList &&
|
|
36411
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
36412
|
+
actions &&
|
|
36413
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
34119
36414
|
}
|
|
34120
36415
|
return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
34121
36416
|
strategyName: context.strategyName,
|
|
@@ -34123,15 +36418,30 @@ class LiveUtils {
|
|
|
34123
36418
|
frameName: "",
|
|
34124
36419
|
});
|
|
34125
36420
|
};
|
|
36421
|
+
/**
|
|
36422
|
+
* Returns the total number of base-asset units currently held in the position.
|
|
36423
|
+
*
|
|
36424
|
+
* Includes units from all DCA entries. Returns null if no pending signal exists.
|
|
36425
|
+
*
|
|
36426
|
+
* @param symbol - Trading pair symbol
|
|
36427
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36428
|
+
* @returns Total units held, or null if no active position
|
|
36429
|
+
*/
|
|
34126
36430
|
this.getPositionInvestedCount = async (symbol, context) => {
|
|
34127
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
36431
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
36432
|
+
symbol,
|
|
36433
|
+
context,
|
|
36434
|
+
});
|
|
34128
36435
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
34129
36436
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
34130
36437
|
{
|
|
34131
36438
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34132
|
-
riskName &&
|
|
34133
|
-
|
|
34134
|
-
|
|
36439
|
+
riskName &&
|
|
36440
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
36441
|
+
riskList &&
|
|
36442
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
36443
|
+
actions &&
|
|
36444
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
34135
36445
|
}
|
|
34136
36446
|
return await bt.strategyCoreService.getPositionInvestedCount(false, symbol, {
|
|
34137
36447
|
strategyName: context.strategyName,
|
|
@@ -34139,15 +36449,30 @@ class LiveUtils {
|
|
|
34139
36449
|
frameName: "",
|
|
34140
36450
|
});
|
|
34141
36451
|
};
|
|
36452
|
+
/**
|
|
36453
|
+
* Returns the total dollar cost invested in the current position.
|
|
36454
|
+
*
|
|
36455
|
+
* Sum of all entry costs across DCA entries. Returns null if no pending signal exists.
|
|
36456
|
+
*
|
|
36457
|
+
* @param symbol - Trading pair symbol
|
|
36458
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36459
|
+
* @returns Total invested cost in quote currency, or null if no active position
|
|
36460
|
+
*/
|
|
34142
36461
|
this.getPositionInvestedCost = async (symbol, context) => {
|
|
34143
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
36462
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
36463
|
+
symbol,
|
|
36464
|
+
context,
|
|
36465
|
+
});
|
|
34144
36466
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
34145
36467
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
34146
36468
|
{
|
|
34147
36469
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34148
|
-
riskName &&
|
|
34149
|
-
|
|
34150
|
-
|
|
36470
|
+
riskName &&
|
|
36471
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
36472
|
+
riskList &&
|
|
36473
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
36474
|
+
actions &&
|
|
36475
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
34151
36476
|
}
|
|
34152
36477
|
return await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
34153
36478
|
strategyName: context.strategyName,
|
|
@@ -34155,15 +36480,33 @@ class LiveUtils {
|
|
|
34155
36480
|
frameName: "",
|
|
34156
36481
|
});
|
|
34157
36482
|
};
|
|
36483
|
+
/**
|
|
36484
|
+
* Returns the current unrealized PnL as a percentage of the invested cost.
|
|
36485
|
+
*
|
|
36486
|
+
* Calculated relative to the effective (weighted average) entry price.
|
|
36487
|
+
* Positive for profit, negative for loss. Returns null if no pending signal exists.
|
|
36488
|
+
*
|
|
36489
|
+
* @param symbol - Trading pair symbol
|
|
36490
|
+
* @param currentPrice - Current market price
|
|
36491
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36492
|
+
* @returns PnL percentage, or null if no active position
|
|
36493
|
+
*/
|
|
34158
36494
|
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
34159
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
36495
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
36496
|
+
symbol,
|
|
36497
|
+
currentPrice,
|
|
36498
|
+
context,
|
|
36499
|
+
});
|
|
34160
36500
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
34161
36501
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
34162
36502
|
{
|
|
34163
36503
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34164
|
-
riskName &&
|
|
34165
|
-
|
|
34166
|
-
|
|
36504
|
+
riskName &&
|
|
36505
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
36506
|
+
riskList &&
|
|
36507
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
36508
|
+
actions &&
|
|
36509
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
34167
36510
|
}
|
|
34168
36511
|
return await bt.strategyCoreService.getPositionPnlPercent(false, symbol, currentPrice, {
|
|
34169
36512
|
strategyName: context.strategyName,
|
|
@@ -34171,15 +36514,33 @@ class LiveUtils {
|
|
|
34171
36514
|
frameName: "",
|
|
34172
36515
|
});
|
|
34173
36516
|
};
|
|
36517
|
+
/**
|
|
36518
|
+
* Returns the current unrealized PnL in quote currency (dollar amount).
|
|
36519
|
+
*
|
|
36520
|
+
* Calculated as (currentPrice - effectiveEntry) * units for LONG,
|
|
36521
|
+
* reversed for SHORT. Returns null if no pending signal exists.
|
|
36522
|
+
*
|
|
36523
|
+
* @param symbol - Trading pair symbol
|
|
36524
|
+
* @param currentPrice - Current market price
|
|
36525
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36526
|
+
* @returns PnL in quote currency, or null if no active position
|
|
36527
|
+
*/
|
|
34174
36528
|
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
34175
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
36529
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
36530
|
+
symbol,
|
|
36531
|
+
currentPrice,
|
|
36532
|
+
context,
|
|
36533
|
+
});
|
|
34176
36534
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
34177
36535
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
34178
36536
|
{
|
|
34179
36537
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34180
|
-
riskName &&
|
|
34181
|
-
|
|
34182
|
-
|
|
36538
|
+
riskName &&
|
|
36539
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
36540
|
+
riskList &&
|
|
36541
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
36542
|
+
actions &&
|
|
36543
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
34183
36544
|
}
|
|
34184
36545
|
return await bt.strategyCoreService.getPositionPnlCost(false, symbol, currentPrice, {
|
|
34185
36546
|
strategyName: context.strategyName,
|
|
@@ -34187,15 +36548,33 @@ class LiveUtils {
|
|
|
34187
36548
|
frameName: "",
|
|
34188
36549
|
});
|
|
34189
36550
|
};
|
|
36551
|
+
/**
|
|
36552
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
36553
|
+
*
|
|
36554
|
+
* The first element is always the original priceOpen (initial entry).
|
|
36555
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
36556
|
+
* Returns null if no pending signal exists.
|
|
36557
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
36558
|
+
*
|
|
36559
|
+
* @param symbol - Trading pair symbol
|
|
36560
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36561
|
+
* @returns Array of entry prices, or null if no active position
|
|
36562
|
+
*/
|
|
34190
36563
|
this.getPositionLevels = async (symbol, context) => {
|
|
34191
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
36564
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
36565
|
+
symbol,
|
|
36566
|
+
context,
|
|
36567
|
+
});
|
|
34192
36568
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
34193
36569
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
34194
36570
|
{
|
|
34195
36571
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34196
|
-
riskName &&
|
|
34197
|
-
|
|
34198
|
-
|
|
36572
|
+
riskName &&
|
|
36573
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
36574
|
+
riskList &&
|
|
36575
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
36576
|
+
actions &&
|
|
36577
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
34199
36578
|
}
|
|
34200
36579
|
return await bt.strategyCoreService.getPositionLevels(false, symbol, {
|
|
34201
36580
|
strategyName: context.strategyName,
|
|
@@ -34203,15 +36582,40 @@ class LiveUtils {
|
|
|
34203
36582
|
frameName: "",
|
|
34204
36583
|
});
|
|
34205
36584
|
};
|
|
36585
|
+
/**
|
|
36586
|
+
* Returns the list of partial close events for the current pending signal.
|
|
36587
|
+
*
|
|
36588
|
+
* Each element represents a partial profit or loss close executed via
|
|
36589
|
+
* commitPartialProfit / commitPartialLoss (or their Cost variants).
|
|
36590
|
+
* Returns null if no pending signal exists.
|
|
36591
|
+
* Returns an empty array if no partials were executed yet.
|
|
36592
|
+
*
|
|
36593
|
+
* Each entry contains:
|
|
36594
|
+
* - `type` — "profit" or "loss"
|
|
36595
|
+
* - `percent` — percentage of position closed at this partial
|
|
36596
|
+
* - `currentPrice` — execution price of the partial close
|
|
36597
|
+
* - `costBasisAtClose` — accounting cost basis at the moment of this partial
|
|
36598
|
+
* - `entryCountAtClose` — number of DCA entries accumulated at this partial
|
|
36599
|
+
*
|
|
36600
|
+
* @param symbol - Trading pair symbol
|
|
36601
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36602
|
+
* @returns Array of partial close records, or null if no active position
|
|
36603
|
+
*/
|
|
34206
36604
|
this.getPositionPartials = async (symbol, context) => {
|
|
34207
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
36605
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
36606
|
+
symbol,
|
|
36607
|
+
context,
|
|
36608
|
+
});
|
|
34208
36609
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
34209
36610
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
34210
36611
|
{
|
|
34211
36612
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34212
|
-
riskName &&
|
|
34213
|
-
|
|
34214
|
-
|
|
36613
|
+
riskName &&
|
|
36614
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
36615
|
+
riskList &&
|
|
36616
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
36617
|
+
actions &&
|
|
36618
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
34215
36619
|
}
|
|
34216
36620
|
return await bt.strategyCoreService.getPositionPartials(false, symbol, {
|
|
34217
36621
|
strategyName: context.strategyName,
|
|
@@ -34245,9 +36649,12 @@ class LiveUtils {
|
|
|
34245
36649
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_STOP);
|
|
34246
36650
|
{
|
|
34247
36651
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34248
|
-
riskName &&
|
|
34249
|
-
|
|
34250
|
-
|
|
36652
|
+
riskName &&
|
|
36653
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_STOP);
|
|
36654
|
+
riskList &&
|
|
36655
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_STOP));
|
|
36656
|
+
actions &&
|
|
36657
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_STOP));
|
|
34251
36658
|
}
|
|
34252
36659
|
await bt.strategyCoreService.stopStrategy(false, symbol, {
|
|
34253
36660
|
strategyName: context.strategyName,
|
|
@@ -34288,9 +36695,12 @@ class LiveUtils {
|
|
|
34288
36695
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
34289
36696
|
{
|
|
34290
36697
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34291
|
-
riskName &&
|
|
34292
|
-
|
|
34293
|
-
|
|
36698
|
+
riskName &&
|
|
36699
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
36700
|
+
riskList &&
|
|
36701
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CANCEL_SCHEDULED));
|
|
36702
|
+
actions &&
|
|
36703
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_CANCEL_SCHEDULED));
|
|
34294
36704
|
}
|
|
34295
36705
|
await bt.strategyCoreService.cancelScheduled(false, symbol, {
|
|
34296
36706
|
strategyName: context.strategyName,
|
|
@@ -34329,9 +36739,12 @@ class LiveUtils {
|
|
|
34329
36739
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
34330
36740
|
{
|
|
34331
36741
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34332
|
-
riskName &&
|
|
34333
|
-
|
|
34334
|
-
|
|
36742
|
+
riskName &&
|
|
36743
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
36744
|
+
riskList &&
|
|
36745
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CLOSE_PENDING));
|
|
36746
|
+
actions &&
|
|
36747
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_CLOSE_PENDING));
|
|
34335
36748
|
}
|
|
34336
36749
|
await bt.strategyCoreService.closePending(false, symbol, {
|
|
34337
36750
|
strategyName: context.strategyName,
|
|
@@ -34378,10 +36791,51 @@ class LiveUtils {
|
|
|
34378
36791
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
|
|
34379
36792
|
{
|
|
34380
36793
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34381
|
-
riskName &&
|
|
34382
|
-
|
|
34383
|
-
|
|
36794
|
+
riskName &&
|
|
36795
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
|
|
36796
|
+
riskList &&
|
|
36797
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT));
|
|
36798
|
+
actions &&
|
|
36799
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_PROFIT));
|
|
36800
|
+
}
|
|
36801
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
36802
|
+
strategyName: context.strategyName,
|
|
36803
|
+
exchangeName: context.exchangeName,
|
|
36804
|
+
frameName: "",
|
|
36805
|
+
});
|
|
36806
|
+
if (investedCost === null) {
|
|
36807
|
+
return false;
|
|
36808
|
+
}
|
|
36809
|
+
const signalForProfit = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
36810
|
+
strategyName: context.strategyName,
|
|
36811
|
+
exchangeName: context.exchangeName,
|
|
36812
|
+
frameName: "",
|
|
36813
|
+
});
|
|
36814
|
+
if (!signalForProfit) {
|
|
36815
|
+
return false;
|
|
36816
|
+
}
|
|
36817
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialProfit(false, symbol, percentToClose, currentPrice, {
|
|
36818
|
+
strategyName: context.strategyName,
|
|
36819
|
+
exchangeName: context.exchangeName,
|
|
36820
|
+
frameName: "",
|
|
36821
|
+
}))) {
|
|
36822
|
+
return false;
|
|
34384
36823
|
}
|
|
36824
|
+
await Broker.commitPartialProfit({
|
|
36825
|
+
symbol,
|
|
36826
|
+
percentToClose,
|
|
36827
|
+
cost: percentToCloseCost(percentToClose, investedCost),
|
|
36828
|
+
currentPrice,
|
|
36829
|
+
position: signalForProfit.position,
|
|
36830
|
+
priceTakeProfit: signalForProfit.priceTakeProfit,
|
|
36831
|
+
priceStopLoss: signalForProfit.priceStopLoss,
|
|
36832
|
+
context: {
|
|
36833
|
+
strategyName: context.strategyName,
|
|
36834
|
+
exchangeName: context.exchangeName,
|
|
36835
|
+
frameName: "",
|
|
36836
|
+
},
|
|
36837
|
+
backtest: false,
|
|
36838
|
+
});
|
|
34385
36839
|
return await bt.strategyCoreService.partialProfit(false, symbol, percentToClose, currentPrice, {
|
|
34386
36840
|
strategyName: context.strategyName,
|
|
34387
36841
|
exchangeName: context.exchangeName,
|
|
@@ -34427,10 +36881,51 @@ class LiveUtils {
|
|
|
34427
36881
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS);
|
|
34428
36882
|
{
|
|
34429
36883
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34430
|
-
riskName &&
|
|
34431
|
-
|
|
34432
|
-
|
|
36884
|
+
riskName &&
|
|
36885
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS);
|
|
36886
|
+
riskList &&
|
|
36887
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS));
|
|
36888
|
+
actions &&
|
|
36889
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_LOSS));
|
|
36890
|
+
}
|
|
36891
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
36892
|
+
strategyName: context.strategyName,
|
|
36893
|
+
exchangeName: context.exchangeName,
|
|
36894
|
+
frameName: "",
|
|
36895
|
+
});
|
|
36896
|
+
if (investedCost === null) {
|
|
36897
|
+
return false;
|
|
36898
|
+
}
|
|
36899
|
+
const signalForLoss = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
36900
|
+
strategyName: context.strategyName,
|
|
36901
|
+
exchangeName: context.exchangeName,
|
|
36902
|
+
frameName: "",
|
|
36903
|
+
});
|
|
36904
|
+
if (!signalForLoss) {
|
|
36905
|
+
return false;
|
|
34433
36906
|
}
|
|
36907
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialLoss(false, symbol, percentToClose, currentPrice, {
|
|
36908
|
+
strategyName: context.strategyName,
|
|
36909
|
+
exchangeName: context.exchangeName,
|
|
36910
|
+
frameName: "",
|
|
36911
|
+
}))) {
|
|
36912
|
+
return false;
|
|
36913
|
+
}
|
|
36914
|
+
await Broker.commitPartialLoss({
|
|
36915
|
+
symbol,
|
|
36916
|
+
percentToClose,
|
|
36917
|
+
cost: percentToCloseCost(percentToClose, investedCost),
|
|
36918
|
+
currentPrice,
|
|
36919
|
+
position: signalForLoss.position,
|
|
36920
|
+
priceTakeProfit: signalForLoss.priceTakeProfit,
|
|
36921
|
+
priceStopLoss: signalForLoss.priceStopLoss,
|
|
36922
|
+
context: {
|
|
36923
|
+
strategyName: context.strategyName,
|
|
36924
|
+
exchangeName: context.exchangeName,
|
|
36925
|
+
frameName: "",
|
|
36926
|
+
},
|
|
36927
|
+
backtest: false,
|
|
36928
|
+
});
|
|
34434
36929
|
return await bt.strategyCoreService.partialLoss(false, symbol, percentToClose, currentPrice, {
|
|
34435
36930
|
strategyName: context.strategyName,
|
|
34436
36931
|
exchangeName: context.exchangeName,
|
|
@@ -34467,23 +36962,62 @@ class LiveUtils {
|
|
|
34467
36962
|
* ```
|
|
34468
36963
|
*/
|
|
34469
36964
|
this.commitPartialProfitCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
34470
|
-
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_PROFIT_COST, {
|
|
36965
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_PROFIT_COST, {
|
|
36966
|
+
symbol,
|
|
36967
|
+
dollarAmount,
|
|
36968
|
+
currentPrice,
|
|
36969
|
+
context,
|
|
36970
|
+
});
|
|
34471
36971
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
34472
36972
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
34473
36973
|
{
|
|
34474
36974
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34475
|
-
riskName &&
|
|
34476
|
-
|
|
34477
|
-
|
|
36975
|
+
riskName &&
|
|
36976
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
36977
|
+
riskList &&
|
|
36978
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
36979
|
+
actions &&
|
|
36980
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
34478
36981
|
}
|
|
34479
36982
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
34480
36983
|
strategyName: context.strategyName,
|
|
34481
36984
|
exchangeName: context.exchangeName,
|
|
34482
36985
|
frameName: "",
|
|
34483
36986
|
});
|
|
34484
|
-
if (investedCost === null)
|
|
36987
|
+
if (investedCost === null) {
|
|
36988
|
+
return false;
|
|
36989
|
+
}
|
|
36990
|
+
const signalForProfitCost = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
36991
|
+
strategyName: context.strategyName,
|
|
36992
|
+
exchangeName: context.exchangeName,
|
|
36993
|
+
frameName: "",
|
|
36994
|
+
});
|
|
36995
|
+
if (!signalForProfitCost) {
|
|
34485
36996
|
return false;
|
|
36997
|
+
}
|
|
34486
36998
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
36999
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialProfit(false, symbol, percentToClose, currentPrice, {
|
|
37000
|
+
strategyName: context.strategyName,
|
|
37001
|
+
exchangeName: context.exchangeName,
|
|
37002
|
+
frameName: "",
|
|
37003
|
+
}))) {
|
|
37004
|
+
return false;
|
|
37005
|
+
}
|
|
37006
|
+
await Broker.commitPartialProfit({
|
|
37007
|
+
symbol,
|
|
37008
|
+
percentToClose,
|
|
37009
|
+
cost: dollarAmount,
|
|
37010
|
+
currentPrice,
|
|
37011
|
+
position: signalForProfitCost.position,
|
|
37012
|
+
priceTakeProfit: signalForProfitCost.priceTakeProfit,
|
|
37013
|
+
priceStopLoss: signalForProfitCost.priceStopLoss,
|
|
37014
|
+
context: {
|
|
37015
|
+
strategyName: context.strategyName,
|
|
37016
|
+
exchangeName: context.exchangeName,
|
|
37017
|
+
frameName: "",
|
|
37018
|
+
},
|
|
37019
|
+
backtest: false,
|
|
37020
|
+
});
|
|
34487
37021
|
return await bt.strategyCoreService.partialProfit(false, symbol, percentToClose, currentPrice, {
|
|
34488
37022
|
strategyName: context.strategyName,
|
|
34489
37023
|
exchangeName: context.exchangeName,
|
|
@@ -34520,23 +37054,62 @@ class LiveUtils {
|
|
|
34520
37054
|
* ```
|
|
34521
37055
|
*/
|
|
34522
37056
|
this.commitPartialLossCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
34523
|
-
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_LOSS_COST, {
|
|
37057
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_LOSS_COST, {
|
|
37058
|
+
symbol,
|
|
37059
|
+
dollarAmount,
|
|
37060
|
+
currentPrice,
|
|
37061
|
+
context,
|
|
37062
|
+
});
|
|
34524
37063
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
34525
37064
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
34526
37065
|
{
|
|
34527
37066
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34528
|
-
riskName &&
|
|
34529
|
-
|
|
34530
|
-
|
|
37067
|
+
riskName &&
|
|
37068
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
37069
|
+
riskList &&
|
|
37070
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
37071
|
+
actions &&
|
|
37072
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
34531
37073
|
}
|
|
34532
37074
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
34533
37075
|
strategyName: context.strategyName,
|
|
34534
37076
|
exchangeName: context.exchangeName,
|
|
34535
37077
|
frameName: "",
|
|
34536
37078
|
});
|
|
34537
|
-
if (investedCost === null)
|
|
37079
|
+
if (investedCost === null) {
|
|
37080
|
+
return false;
|
|
37081
|
+
}
|
|
37082
|
+
const signalForLossCost = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37083
|
+
strategyName: context.strategyName,
|
|
37084
|
+
exchangeName: context.exchangeName,
|
|
37085
|
+
frameName: "",
|
|
37086
|
+
});
|
|
37087
|
+
if (!signalForLossCost) {
|
|
34538
37088
|
return false;
|
|
37089
|
+
}
|
|
34539
37090
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
37091
|
+
if (await functoolsKit.not(bt.strategyCoreService.validatePartialLoss(false, symbol, percentToClose, currentPrice, {
|
|
37092
|
+
strategyName: context.strategyName,
|
|
37093
|
+
exchangeName: context.exchangeName,
|
|
37094
|
+
frameName: "",
|
|
37095
|
+
}))) {
|
|
37096
|
+
return false;
|
|
37097
|
+
}
|
|
37098
|
+
await Broker.commitPartialLoss({
|
|
37099
|
+
symbol,
|
|
37100
|
+
percentToClose,
|
|
37101
|
+
cost: dollarAmount,
|
|
37102
|
+
currentPrice,
|
|
37103
|
+
position: signalForLossCost.position,
|
|
37104
|
+
priceTakeProfit: signalForLossCost.priceTakeProfit,
|
|
37105
|
+
priceStopLoss: signalForLossCost.priceStopLoss,
|
|
37106
|
+
context: {
|
|
37107
|
+
strategyName: context.strategyName,
|
|
37108
|
+
exchangeName: context.exchangeName,
|
|
37109
|
+
frameName: "",
|
|
37110
|
+
},
|
|
37111
|
+
backtest: false,
|
|
37112
|
+
});
|
|
34540
37113
|
return await bt.strategyCoreService.partialLoss(false, symbol, percentToClose, currentPrice, {
|
|
34541
37114
|
strategyName: context.strategyName,
|
|
34542
37115
|
exchangeName: context.exchangeName,
|
|
@@ -34597,10 +37170,45 @@ class LiveUtils {
|
|
|
34597
37170
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_STOP);
|
|
34598
37171
|
{
|
|
34599
37172
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34600
|
-
riskName &&
|
|
34601
|
-
|
|
34602
|
-
|
|
37173
|
+
riskName &&
|
|
37174
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP);
|
|
37175
|
+
riskList &&
|
|
37176
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP));
|
|
37177
|
+
actions &&
|
|
37178
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_STOP));
|
|
34603
37179
|
}
|
|
37180
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37181
|
+
strategyName: context.strategyName,
|
|
37182
|
+
exchangeName: context.exchangeName,
|
|
37183
|
+
frameName: "",
|
|
37184
|
+
});
|
|
37185
|
+
if (!signal) {
|
|
37186
|
+
return false;
|
|
37187
|
+
}
|
|
37188
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37189
|
+
strategyName: context.strategyName,
|
|
37190
|
+
exchangeName: context.exchangeName,
|
|
37191
|
+
frameName: "",
|
|
37192
|
+
});
|
|
37193
|
+
if (effectivePriceOpen === null) {
|
|
37194
|
+
return false;
|
|
37195
|
+
}
|
|
37196
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingStop(false, symbol, percentShift, currentPrice, {
|
|
37197
|
+
strategyName: context.strategyName,
|
|
37198
|
+
exchangeName: context.exchangeName,
|
|
37199
|
+
frameName: "",
|
|
37200
|
+
}))) {
|
|
37201
|
+
return false;
|
|
37202
|
+
}
|
|
37203
|
+
await Broker.commitTrailingStop({
|
|
37204
|
+
symbol,
|
|
37205
|
+
percentShift,
|
|
37206
|
+
currentPrice,
|
|
37207
|
+
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
37208
|
+
position: signal.position,
|
|
37209
|
+
context,
|
|
37210
|
+
backtest: false,
|
|
37211
|
+
});
|
|
34604
37212
|
return await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
|
|
34605
37213
|
strategyName: context.strategyName,
|
|
34606
37214
|
exchangeName: context.exchangeName,
|
|
@@ -34661,10 +37269,183 @@ class LiveUtils {
|
|
|
34661
37269
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_PROFIT);
|
|
34662
37270
|
{
|
|
34663
37271
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34664
|
-
riskName &&
|
|
34665
|
-
|
|
34666
|
-
|
|
37272
|
+
riskName &&
|
|
37273
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT);
|
|
37274
|
+
riskList &&
|
|
37275
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT));
|
|
37276
|
+
actions &&
|
|
37277
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_PROFIT));
|
|
37278
|
+
}
|
|
37279
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37280
|
+
strategyName: context.strategyName,
|
|
37281
|
+
exchangeName: context.exchangeName,
|
|
37282
|
+
frameName: "",
|
|
37283
|
+
});
|
|
37284
|
+
if (!signal) {
|
|
37285
|
+
return false;
|
|
37286
|
+
}
|
|
37287
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37288
|
+
strategyName: context.strategyName,
|
|
37289
|
+
exchangeName: context.exchangeName,
|
|
37290
|
+
frameName: "",
|
|
37291
|
+
});
|
|
37292
|
+
if (effectivePriceOpen === null) {
|
|
37293
|
+
return false;
|
|
34667
37294
|
}
|
|
37295
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingTake(false, symbol, percentShift, currentPrice, {
|
|
37296
|
+
strategyName: context.strategyName,
|
|
37297
|
+
exchangeName: context.exchangeName,
|
|
37298
|
+
frameName: "",
|
|
37299
|
+
}))) {
|
|
37300
|
+
return false;
|
|
37301
|
+
}
|
|
37302
|
+
await Broker.commitTrailingTake({
|
|
37303
|
+
symbol,
|
|
37304
|
+
percentShift,
|
|
37305
|
+
currentPrice,
|
|
37306
|
+
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
37307
|
+
position: signal.position,
|
|
37308
|
+
context,
|
|
37309
|
+
backtest: false,
|
|
37310
|
+
});
|
|
37311
|
+
return await bt.strategyCoreService.trailingTake(false, symbol, percentShift, currentPrice, {
|
|
37312
|
+
strategyName: context.strategyName,
|
|
37313
|
+
exchangeName: context.exchangeName,
|
|
37314
|
+
frameName: "",
|
|
37315
|
+
});
|
|
37316
|
+
};
|
|
37317
|
+
/**
|
|
37318
|
+
* Adjusts the trailing stop-loss to an absolute price level.
|
|
37319
|
+
*
|
|
37320
|
+
* Convenience wrapper around commitTrailingStop that converts an absolute
|
|
37321
|
+
* stop-loss price to a percentShift relative to the ORIGINAL SL distance.
|
|
37322
|
+
*
|
|
37323
|
+
* @param symbol - Trading pair symbol
|
|
37324
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
37325
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
37326
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37327
|
+
* @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
|
|
37328
|
+
*/
|
|
37329
|
+
this.commitTrailingStopCost = async (symbol, newStopLossPrice, currentPrice, context) => {
|
|
37330
|
+
bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_STOP_COST, {
|
|
37331
|
+
symbol,
|
|
37332
|
+
newStopLossPrice,
|
|
37333
|
+
currentPrice,
|
|
37334
|
+
context,
|
|
37335
|
+
});
|
|
37336
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_TRAILING_STOP_COST);
|
|
37337
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_STOP_COST);
|
|
37338
|
+
{
|
|
37339
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37340
|
+
riskName &&
|
|
37341
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP_COST);
|
|
37342
|
+
riskList &&
|
|
37343
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP_COST));
|
|
37344
|
+
actions &&
|
|
37345
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_STOP_COST));
|
|
37346
|
+
}
|
|
37347
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37348
|
+
strategyName: context.strategyName,
|
|
37349
|
+
exchangeName: context.exchangeName,
|
|
37350
|
+
frameName: "",
|
|
37351
|
+
});
|
|
37352
|
+
if (!signal) {
|
|
37353
|
+
return false;
|
|
37354
|
+
}
|
|
37355
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37356
|
+
strategyName: context.strategyName,
|
|
37357
|
+
exchangeName: context.exchangeName,
|
|
37358
|
+
frameName: "",
|
|
37359
|
+
});
|
|
37360
|
+
if (effectivePriceOpen === null) {
|
|
37361
|
+
return false;
|
|
37362
|
+
}
|
|
37363
|
+
const percentShift = slPriceToPercentShift(newStopLossPrice, signal.priceStopLoss, effectivePriceOpen);
|
|
37364
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingStop(false, symbol, percentShift, currentPrice, {
|
|
37365
|
+
strategyName: context.strategyName,
|
|
37366
|
+
exchangeName: context.exchangeName,
|
|
37367
|
+
frameName: "",
|
|
37368
|
+
}))) {
|
|
37369
|
+
return false;
|
|
37370
|
+
}
|
|
37371
|
+
await Broker.commitTrailingStop({
|
|
37372
|
+
symbol,
|
|
37373
|
+
percentShift,
|
|
37374
|
+
currentPrice,
|
|
37375
|
+
newStopLossPrice,
|
|
37376
|
+
position: signal.position,
|
|
37377
|
+
context,
|
|
37378
|
+
backtest: false,
|
|
37379
|
+
});
|
|
37380
|
+
return await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
|
|
37381
|
+
strategyName: context.strategyName,
|
|
37382
|
+
exchangeName: context.exchangeName,
|
|
37383
|
+
frameName: "",
|
|
37384
|
+
});
|
|
37385
|
+
};
|
|
37386
|
+
/**
|
|
37387
|
+
* Adjusts the trailing take-profit to an absolute price level.
|
|
37388
|
+
*
|
|
37389
|
+
* Convenience wrapper around commitTrailingTake that converts an absolute
|
|
37390
|
+
* take-profit price to a percentShift relative to the ORIGINAL TP distance.
|
|
37391
|
+
*
|
|
37392
|
+
* @param symbol - Trading pair symbol
|
|
37393
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
37394
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
37395
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37396
|
+
* @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
|
|
37397
|
+
*/
|
|
37398
|
+
this.commitTrailingTakeCost = async (symbol, newTakeProfitPrice, currentPrice, context) => {
|
|
37399
|
+
bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_PROFIT_COST, {
|
|
37400
|
+
symbol,
|
|
37401
|
+
newTakeProfitPrice,
|
|
37402
|
+
currentPrice,
|
|
37403
|
+
context,
|
|
37404
|
+
});
|
|
37405
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
37406
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
37407
|
+
{
|
|
37408
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37409
|
+
riskName &&
|
|
37410
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
37411
|
+
riskList &&
|
|
37412
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
37413
|
+
actions &&
|
|
37414
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
37415
|
+
}
|
|
37416
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37417
|
+
strategyName: context.strategyName,
|
|
37418
|
+
exchangeName: context.exchangeName,
|
|
37419
|
+
frameName: "",
|
|
37420
|
+
});
|
|
37421
|
+
if (!signal) {
|
|
37422
|
+
return false;
|
|
37423
|
+
}
|
|
37424
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37425
|
+
strategyName: context.strategyName,
|
|
37426
|
+
exchangeName: context.exchangeName,
|
|
37427
|
+
frameName: "",
|
|
37428
|
+
});
|
|
37429
|
+
if (effectivePriceOpen === null) {
|
|
37430
|
+
return false;
|
|
37431
|
+
}
|
|
37432
|
+
const percentShift = tpPriceToPercentShift(newTakeProfitPrice, signal.priceTakeProfit, effectivePriceOpen);
|
|
37433
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateTrailingTake(false, symbol, percentShift, currentPrice, {
|
|
37434
|
+
strategyName: context.strategyName,
|
|
37435
|
+
exchangeName: context.exchangeName,
|
|
37436
|
+
frameName: "",
|
|
37437
|
+
}))) {
|
|
37438
|
+
return false;
|
|
37439
|
+
}
|
|
37440
|
+
await Broker.commitTrailingTake({
|
|
37441
|
+
symbol,
|
|
37442
|
+
percentShift,
|
|
37443
|
+
currentPrice,
|
|
37444
|
+
newTakeProfitPrice,
|
|
37445
|
+
position: signal.position,
|
|
37446
|
+
context,
|
|
37447
|
+
backtest: false,
|
|
37448
|
+
});
|
|
34668
37449
|
return await bt.strategyCoreService.trailingTake(false, symbol, percentShift, currentPrice, {
|
|
34669
37450
|
strategyName: context.strategyName,
|
|
34670
37451
|
exchangeName: context.exchangeName,
|
|
@@ -34702,10 +37483,49 @@ class LiveUtils {
|
|
|
34702
37483
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_BREAKEVEN);
|
|
34703
37484
|
{
|
|
34704
37485
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34705
|
-
riskName &&
|
|
34706
|
-
|
|
34707
|
-
|
|
37486
|
+
riskName &&
|
|
37487
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BREAKEVEN);
|
|
37488
|
+
riskList &&
|
|
37489
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BREAKEVEN));
|
|
37490
|
+
actions &&
|
|
37491
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_BREAKEVEN));
|
|
37492
|
+
}
|
|
37493
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37494
|
+
strategyName: context.strategyName,
|
|
37495
|
+
exchangeName: context.exchangeName,
|
|
37496
|
+
frameName: "",
|
|
37497
|
+
});
|
|
37498
|
+
if (!signal) {
|
|
37499
|
+
return false;
|
|
37500
|
+
}
|
|
37501
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37502
|
+
strategyName: context.strategyName,
|
|
37503
|
+
exchangeName: context.exchangeName,
|
|
37504
|
+
frameName: "",
|
|
37505
|
+
});
|
|
37506
|
+
if (effectivePriceOpen === null) {
|
|
37507
|
+
return false;
|
|
34708
37508
|
}
|
|
37509
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateBreakeven(false, symbol, currentPrice, {
|
|
37510
|
+
strategyName: context.strategyName,
|
|
37511
|
+
exchangeName: context.exchangeName,
|
|
37512
|
+
frameName: "",
|
|
37513
|
+
}))) {
|
|
37514
|
+
return false;
|
|
37515
|
+
}
|
|
37516
|
+
await Broker.commitBreakeven({
|
|
37517
|
+
symbol,
|
|
37518
|
+
currentPrice,
|
|
37519
|
+
newStopLossPrice: breakevenNewStopLossPrice(effectivePriceOpen),
|
|
37520
|
+
newTakeProfitPrice: breakevenNewTakeProfitPrice(signal.priceTakeProfit, signal._trailingPriceTakeProfit),
|
|
37521
|
+
position: signal.position,
|
|
37522
|
+
context: {
|
|
37523
|
+
strategyName: context.strategyName,
|
|
37524
|
+
exchangeName: context.exchangeName,
|
|
37525
|
+
frameName: "",
|
|
37526
|
+
},
|
|
37527
|
+
backtest: false,
|
|
37528
|
+
});
|
|
34709
37529
|
return await bt.strategyCoreService.breakeven(false, symbol, currentPrice, {
|
|
34710
37530
|
strategyName: context.strategyName,
|
|
34711
37531
|
exchangeName: context.exchangeName,
|
|
@@ -34742,9 +37562,12 @@ class LiveUtils {
|
|
|
34742
37562
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
34743
37563
|
{
|
|
34744
37564
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34745
|
-
riskName &&
|
|
34746
|
-
|
|
34747
|
-
|
|
37565
|
+
riskName &&
|
|
37566
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
37567
|
+
riskList &&
|
|
37568
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
37569
|
+
actions &&
|
|
37570
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
34748
37571
|
}
|
|
34749
37572
|
await bt.strategyCoreService.activateScheduled(false, symbol, {
|
|
34750
37573
|
strategyName: context.strategyName,
|
|
@@ -34785,10 +37608,42 @@ class LiveUtils {
|
|
|
34785
37608
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_AVERAGE_BUY);
|
|
34786
37609
|
{
|
|
34787
37610
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34788
|
-
riskName &&
|
|
34789
|
-
|
|
34790
|
-
|
|
37611
|
+
riskName &&
|
|
37612
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY);
|
|
37613
|
+
riskList &&
|
|
37614
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY));
|
|
37615
|
+
actions &&
|
|
37616
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_AVERAGE_BUY));
|
|
37617
|
+
}
|
|
37618
|
+
if (await functoolsKit.not(bt.strategyCoreService.validateAverageBuy(false, symbol, currentPrice, {
|
|
37619
|
+
strategyName: context.strategyName,
|
|
37620
|
+
exchangeName: context.exchangeName,
|
|
37621
|
+
frameName: "",
|
|
37622
|
+
}))) {
|
|
37623
|
+
return false;
|
|
37624
|
+
}
|
|
37625
|
+
const signalForAvgBuy = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37626
|
+
strategyName: context.strategyName,
|
|
37627
|
+
exchangeName: context.exchangeName,
|
|
37628
|
+
frameName: "",
|
|
37629
|
+
});
|
|
37630
|
+
if (!signalForAvgBuy) {
|
|
37631
|
+
return false;
|
|
34791
37632
|
}
|
|
37633
|
+
await Broker.commitAverageBuy({
|
|
37634
|
+
symbol,
|
|
37635
|
+
currentPrice,
|
|
37636
|
+
cost,
|
|
37637
|
+
position: signalForAvgBuy.position,
|
|
37638
|
+
priceTakeProfit: signalForAvgBuy.priceTakeProfit,
|
|
37639
|
+
priceStopLoss: signalForAvgBuy.priceStopLoss,
|
|
37640
|
+
context: {
|
|
37641
|
+
strategyName: context.strategyName,
|
|
37642
|
+
exchangeName: context.exchangeName,
|
|
37643
|
+
frameName: "",
|
|
37644
|
+
},
|
|
37645
|
+
backtest: false,
|
|
37646
|
+
});
|
|
34792
37647
|
return await bt.strategyCoreService.averageBuy(false, symbol, currentPrice, {
|
|
34793
37648
|
strategyName: context.strategyName,
|
|
34794
37649
|
exchangeName: context.exchangeName,
|
|
@@ -34822,9 +37677,12 @@ class LiveUtils {
|
|
|
34822
37677
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_DATA);
|
|
34823
37678
|
{
|
|
34824
37679
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34825
|
-
riskName &&
|
|
34826
|
-
|
|
34827
|
-
|
|
37680
|
+
riskName &&
|
|
37681
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_DATA);
|
|
37682
|
+
riskList &&
|
|
37683
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_DATA));
|
|
37684
|
+
actions &&
|
|
37685
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_DATA));
|
|
34828
37686
|
}
|
|
34829
37687
|
return await bt.liveMarkdownService.getData(symbol, context.strategyName, context.exchangeName, "", false);
|
|
34830
37688
|
};
|
|
@@ -34856,9 +37714,12 @@ class LiveUtils {
|
|
|
34856
37714
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_REPORT);
|
|
34857
37715
|
{
|
|
34858
37716
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34859
|
-
riskName &&
|
|
34860
|
-
|
|
34861
|
-
|
|
37717
|
+
riskName &&
|
|
37718
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT);
|
|
37719
|
+
riskList &&
|
|
37720
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
|
|
37721
|
+
actions &&
|
|
37722
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_REPORT));
|
|
34862
37723
|
}
|
|
34863
37724
|
return await bt.liveMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, "", false, columns);
|
|
34864
37725
|
};
|
|
@@ -34898,9 +37759,12 @@ class LiveUtils {
|
|
|
34898
37759
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_DUMP);
|
|
34899
37760
|
{
|
|
34900
37761
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34901
|
-
riskName &&
|
|
34902
|
-
|
|
34903
|
-
|
|
37762
|
+
riskName &&
|
|
37763
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP);
|
|
37764
|
+
riskList &&
|
|
37765
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
|
|
37766
|
+
actions &&
|
|
37767
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_DUMP));
|
|
34904
37768
|
}
|
|
34905
37769
|
await bt.liveMarkdownService.dump(symbol, context.strategyName, context.exchangeName, "", false, path, columns);
|
|
34906
37770
|
};
|
|
@@ -42491,6 +45355,8 @@ const percentValue = (yesterdayValue, todayValue) => {
|
|
|
42491
45355
|
exports.ActionBase = ActionBase;
|
|
42492
45356
|
exports.Backtest = Backtest;
|
|
42493
45357
|
exports.Breakeven = Breakeven;
|
|
45358
|
+
exports.Broker = Broker;
|
|
45359
|
+
exports.BrokerBase = BrokerBase;
|
|
42494
45360
|
exports.Cache = Cache;
|
|
42495
45361
|
exports.Constant = Constant;
|
|
42496
45362
|
exports.Exchange = Exchange;
|
|
@@ -42548,7 +45414,9 @@ exports.commitPartialLossCost = commitPartialLossCost;
|
|
|
42548
45414
|
exports.commitPartialProfit = commitPartialProfit;
|
|
42549
45415
|
exports.commitPartialProfitCost = commitPartialProfitCost;
|
|
42550
45416
|
exports.commitTrailingStop = commitTrailingStop;
|
|
45417
|
+
exports.commitTrailingStopCost = commitTrailingStopCost;
|
|
42551
45418
|
exports.commitTrailingTake = commitTrailingTake;
|
|
45419
|
+
exports.commitTrailingTakeCost = commitTrailingTakeCost;
|
|
42552
45420
|
exports.dumpMessages = dumpMessages;
|
|
42553
45421
|
exports.emitters = emitters;
|
|
42554
45422
|
exports.formatPrice = formatPrice;
|
|
@@ -42646,6 +45514,7 @@ exports.overrideStrategySchema = overrideStrategySchema;
|
|
|
42646
45514
|
exports.overrideWalkerSchema = overrideWalkerSchema;
|
|
42647
45515
|
exports.parseArgs = parseArgs;
|
|
42648
45516
|
exports.percentDiff = percentDiff;
|
|
45517
|
+
exports.percentToCloseCost = percentToCloseCost;
|
|
42649
45518
|
exports.percentValue = percentValue;
|
|
42650
45519
|
exports.roundTicks = roundTicks;
|
|
42651
45520
|
exports.set = set;
|
|
@@ -42653,8 +45522,12 @@ exports.setColumns = setColumns;
|
|
|
42653
45522
|
exports.setConfig = setConfig;
|
|
42654
45523
|
exports.setLogger = setLogger;
|
|
42655
45524
|
exports.shutdown = shutdown;
|
|
45525
|
+
exports.slPercentShiftToPrice = slPercentShiftToPrice;
|
|
45526
|
+
exports.slPriceToPercentShift = slPriceToPercentShift;
|
|
42656
45527
|
exports.stopStrategy = stopStrategy;
|
|
42657
45528
|
exports.toProfitLossDto = toProfitLossDto;
|
|
45529
|
+
exports.tpPercentShiftToPrice = tpPercentShiftToPrice;
|
|
45530
|
+
exports.tpPriceToPercentShift = tpPriceToPercentShift;
|
|
42658
45531
|
exports.validate = validate;
|
|
42659
45532
|
exports.waitForCandle = waitForCandle;
|
|
42660
45533
|
exports.warmCandles = warmCandles;
|