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.mjs
CHANGED
|
@@ -7359,6 +7359,52 @@ class ClientStrategy {
|
|
|
7359
7359
|
await PersistSignalAdapter.writeSignalData(this._pendingSignal, symbol, this.params.strategyName, this.params.exchangeName);
|
|
7360
7360
|
// Commit will be emitted in tick() with correct currentTime
|
|
7361
7361
|
}
|
|
7362
|
+
/**
|
|
7363
|
+
* Validates preconditions for partialProfit without mutating state.
|
|
7364
|
+
*
|
|
7365
|
+
* Returns false (never throws) when any condition would cause partialProfit to fail or skip.
|
|
7366
|
+
* Use this to pre-check before calling partialProfit to avoid needing to handle exceptions.
|
|
7367
|
+
*
|
|
7368
|
+
* @param symbol - Trading pair symbol
|
|
7369
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
7370
|
+
* @param currentPrice - Current market price (must be in profit direction)
|
|
7371
|
+
* @returns boolean - true if partialProfit would execute, false otherwise
|
|
7372
|
+
*/
|
|
7373
|
+
async validatePartialProfit(symbol, percentToClose, currentPrice) {
|
|
7374
|
+
this.params.logger.debug("ClientStrategy validatePartialProfit", {
|
|
7375
|
+
symbol,
|
|
7376
|
+
percentToClose,
|
|
7377
|
+
currentPrice,
|
|
7378
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7379
|
+
});
|
|
7380
|
+
if (!this._pendingSignal)
|
|
7381
|
+
return false;
|
|
7382
|
+
if (typeof percentToClose !== "number" || !isFinite(percentToClose))
|
|
7383
|
+
return false;
|
|
7384
|
+
if (percentToClose <= 0)
|
|
7385
|
+
return false;
|
|
7386
|
+
if (percentToClose > 100)
|
|
7387
|
+
return false;
|
|
7388
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7389
|
+
return false;
|
|
7390
|
+
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7391
|
+
if (this._pendingSignal.position === "long" && currentPrice <= effectivePriceOpen)
|
|
7392
|
+
return false;
|
|
7393
|
+
if (this._pendingSignal.position === "short" && currentPrice >= effectivePriceOpen)
|
|
7394
|
+
return false;
|
|
7395
|
+
const effectiveTakeProfit = this._pendingSignal._trailingPriceTakeProfit ?? this._pendingSignal.priceTakeProfit;
|
|
7396
|
+
if (this._pendingSignal.position === "long" && currentPrice >= effectiveTakeProfit)
|
|
7397
|
+
return false;
|
|
7398
|
+
if (this._pendingSignal.position === "short" && currentPrice <= effectiveTakeProfit)
|
|
7399
|
+
return false;
|
|
7400
|
+
const { totalClosedPercent, remainingCostBasis } = getTotalClosed(this._pendingSignal);
|
|
7401
|
+
const totalInvested = (this._pendingSignal._entry ?? []).reduce((s, e) => s + e.cost, 0) || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST;
|
|
7402
|
+
const newPartialDollar = (percentToClose / 100) * remainingCostBasis;
|
|
7403
|
+
const newTotalClosedDollar = (totalClosedPercent / 100) * totalInvested + newPartialDollar;
|
|
7404
|
+
if (newTotalClosedDollar > totalInvested)
|
|
7405
|
+
return false;
|
|
7406
|
+
return true;
|
|
7407
|
+
}
|
|
7362
7408
|
/**
|
|
7363
7409
|
* Executes partial close at profit level (moving toward TP).
|
|
7364
7410
|
*
|
|
@@ -7494,6 +7540,52 @@ class ClientStrategy {
|
|
|
7494
7540
|
});
|
|
7495
7541
|
return true;
|
|
7496
7542
|
}
|
|
7543
|
+
/**
|
|
7544
|
+
* Validates preconditions for partialLoss without mutating state.
|
|
7545
|
+
*
|
|
7546
|
+
* Returns false (never throws) when any condition would cause partialLoss to fail or skip.
|
|
7547
|
+
* Use this to pre-check before calling partialLoss to avoid needing to handle exceptions.
|
|
7548
|
+
*
|
|
7549
|
+
* @param symbol - Trading pair symbol
|
|
7550
|
+
* @param percentToClose - Percentage of position to close (0-100)
|
|
7551
|
+
* @param currentPrice - Current market price (must be in loss direction)
|
|
7552
|
+
* @returns boolean - true if partialLoss would execute, false otherwise
|
|
7553
|
+
*/
|
|
7554
|
+
async validatePartialLoss(symbol, percentToClose, currentPrice) {
|
|
7555
|
+
this.params.logger.debug("ClientStrategy validatePartialLoss", {
|
|
7556
|
+
symbol,
|
|
7557
|
+
percentToClose,
|
|
7558
|
+
currentPrice,
|
|
7559
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7560
|
+
});
|
|
7561
|
+
if (!this._pendingSignal)
|
|
7562
|
+
return false;
|
|
7563
|
+
if (typeof percentToClose !== "number" || !isFinite(percentToClose))
|
|
7564
|
+
return false;
|
|
7565
|
+
if (percentToClose <= 0)
|
|
7566
|
+
return false;
|
|
7567
|
+
if (percentToClose > 100)
|
|
7568
|
+
return false;
|
|
7569
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7570
|
+
return false;
|
|
7571
|
+
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7572
|
+
if (this._pendingSignal.position === "long" && currentPrice >= effectivePriceOpen)
|
|
7573
|
+
return false;
|
|
7574
|
+
if (this._pendingSignal.position === "short" && currentPrice <= effectivePriceOpen)
|
|
7575
|
+
return false;
|
|
7576
|
+
const effectiveStopLoss = this._pendingSignal._trailingPriceStopLoss ?? this._pendingSignal.priceStopLoss;
|
|
7577
|
+
if (this._pendingSignal.position === "long" && currentPrice <= effectiveStopLoss)
|
|
7578
|
+
return false;
|
|
7579
|
+
if (this._pendingSignal.position === "short" && currentPrice >= effectiveStopLoss)
|
|
7580
|
+
return false;
|
|
7581
|
+
const { totalClosedPercent, remainingCostBasis } = getTotalClosed(this._pendingSignal);
|
|
7582
|
+
const totalInvested = (this._pendingSignal._entry ?? []).reduce((s, e) => s + e.cost, 0) || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST;
|
|
7583
|
+
const newPartialDollar = (percentToClose / 100) * remainingCostBasis;
|
|
7584
|
+
const newTotalClosedDollar = (totalClosedPercent / 100) * totalInvested + newPartialDollar;
|
|
7585
|
+
if (newTotalClosedDollar > totalInvested)
|
|
7586
|
+
return false;
|
|
7587
|
+
return true;
|
|
7588
|
+
}
|
|
7497
7589
|
/**
|
|
7498
7590
|
* Executes partial close at loss level (moving toward SL).
|
|
7499
7591
|
*
|
|
@@ -7629,6 +7721,82 @@ class ClientStrategy {
|
|
|
7629
7721
|
});
|
|
7630
7722
|
return true;
|
|
7631
7723
|
}
|
|
7724
|
+
/**
|
|
7725
|
+
* Validates preconditions for breakeven without mutating state.
|
|
7726
|
+
*
|
|
7727
|
+
* Returns false (never throws) when any condition would cause breakeven to fail or skip.
|
|
7728
|
+
* Mirrors the full BREAKEVEN_FN condition logic including threshold, trailing state and intrusion checks.
|
|
7729
|
+
*
|
|
7730
|
+
* @param symbol - Trading pair symbol
|
|
7731
|
+
* @param currentPrice - Current market price to check threshold
|
|
7732
|
+
* @returns boolean - true if breakeven would execute, false otherwise
|
|
7733
|
+
*/
|
|
7734
|
+
async validateBreakeven(symbol, currentPrice) {
|
|
7735
|
+
this.params.logger.debug("ClientStrategy validateBreakeven", {
|
|
7736
|
+
symbol,
|
|
7737
|
+
currentPrice,
|
|
7738
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7739
|
+
});
|
|
7740
|
+
if (!this._pendingSignal)
|
|
7741
|
+
return false;
|
|
7742
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7743
|
+
return false;
|
|
7744
|
+
const signal = this._pendingSignal;
|
|
7745
|
+
const breakevenPrice = getEffectivePriceOpen(signal);
|
|
7746
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
7747
|
+
if (signal.position === "long" && breakevenPrice >= effectiveTakeProfit)
|
|
7748
|
+
return false;
|
|
7749
|
+
if (signal.position === "short" && breakevenPrice <= effectiveTakeProfit)
|
|
7750
|
+
return false;
|
|
7751
|
+
const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2;
|
|
7752
|
+
if (signal._trailingPriceStopLoss !== undefined) {
|
|
7753
|
+
const trailingStopLoss = signal._trailingPriceStopLoss;
|
|
7754
|
+
if (signal.position === "long") {
|
|
7755
|
+
const isPositiveTrailing = trailingStopLoss > breakevenPrice;
|
|
7756
|
+
if (isPositiveTrailing)
|
|
7757
|
+
return true; // already protecting profit
|
|
7758
|
+
const thresholdPrice = breakevenPrice * (1 + breakevenThresholdPercent / 100);
|
|
7759
|
+
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
7760
|
+
if (!isThresholdReached || breakevenPrice <= trailingStopLoss)
|
|
7761
|
+
return false;
|
|
7762
|
+
if (currentPrice < breakevenPrice)
|
|
7763
|
+
return false; // price intrusion
|
|
7764
|
+
return true;
|
|
7765
|
+
}
|
|
7766
|
+
else {
|
|
7767
|
+
const isPositiveTrailing = trailingStopLoss < breakevenPrice;
|
|
7768
|
+
if (isPositiveTrailing)
|
|
7769
|
+
return true; // already protecting profit
|
|
7770
|
+
const thresholdPrice = breakevenPrice * (1 - breakevenThresholdPercent / 100);
|
|
7771
|
+
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
7772
|
+
if (!isThresholdReached || breakevenPrice >= trailingStopLoss)
|
|
7773
|
+
return false;
|
|
7774
|
+
if (currentPrice > breakevenPrice)
|
|
7775
|
+
return false; // price intrusion
|
|
7776
|
+
return true;
|
|
7777
|
+
}
|
|
7778
|
+
}
|
|
7779
|
+
const currentStopLoss = signal.priceStopLoss;
|
|
7780
|
+
if (signal.position === "long") {
|
|
7781
|
+
const thresholdPrice = breakevenPrice * (1 + breakevenThresholdPercent / 100);
|
|
7782
|
+
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
7783
|
+
const canMove = isThresholdReached && currentStopLoss < breakevenPrice;
|
|
7784
|
+
if (!canMove)
|
|
7785
|
+
return false;
|
|
7786
|
+
if (currentPrice < breakevenPrice)
|
|
7787
|
+
return false;
|
|
7788
|
+
}
|
|
7789
|
+
else {
|
|
7790
|
+
const thresholdPrice = breakevenPrice * (1 - breakevenThresholdPercent / 100);
|
|
7791
|
+
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
7792
|
+
const canMove = isThresholdReached && currentStopLoss > breakevenPrice;
|
|
7793
|
+
if (!canMove)
|
|
7794
|
+
return false;
|
|
7795
|
+
if (currentPrice > breakevenPrice)
|
|
7796
|
+
return false;
|
|
7797
|
+
}
|
|
7798
|
+
return true;
|
|
7799
|
+
}
|
|
7632
7800
|
/**
|
|
7633
7801
|
* Moves stop-loss to breakeven (entry price) when price reaches threshold.
|
|
7634
7802
|
*
|
|
@@ -7751,6 +7919,65 @@ class ClientStrategy {
|
|
|
7751
7919
|
});
|
|
7752
7920
|
return true;
|
|
7753
7921
|
}
|
|
7922
|
+
/**
|
|
7923
|
+
* Validates preconditions for trailingStop without mutating state.
|
|
7924
|
+
*
|
|
7925
|
+
* Returns false (never throws) when any condition would cause trailingStop to fail or skip.
|
|
7926
|
+
* Includes absorption check: returns false if new SL would not improve on the current trailing SL.
|
|
7927
|
+
*
|
|
7928
|
+
* @param symbol - Trading pair symbol
|
|
7929
|
+
* @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
|
|
7930
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
7931
|
+
* @returns boolean - true if trailingStop would execute, false otherwise
|
|
7932
|
+
*/
|
|
7933
|
+
async validateTrailingStop(symbol, percentShift, currentPrice) {
|
|
7934
|
+
this.params.logger.debug("ClientStrategy validateTrailingStop", {
|
|
7935
|
+
symbol,
|
|
7936
|
+
percentShift,
|
|
7937
|
+
currentPrice,
|
|
7938
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
7939
|
+
});
|
|
7940
|
+
if (!this._pendingSignal)
|
|
7941
|
+
return false;
|
|
7942
|
+
if (typeof percentShift !== "number" || !isFinite(percentShift))
|
|
7943
|
+
return false;
|
|
7944
|
+
if (percentShift < -100 || percentShift > 100)
|
|
7945
|
+
return false;
|
|
7946
|
+
if (percentShift === 0)
|
|
7947
|
+
return false;
|
|
7948
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7949
|
+
return false;
|
|
7950
|
+
const signal = this._pendingSignal;
|
|
7951
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
7952
|
+
const slDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
|
|
7953
|
+
const newSlDistancePercent = slDistancePercent + percentShift;
|
|
7954
|
+
let newStopLoss;
|
|
7955
|
+
if (signal.position === "long") {
|
|
7956
|
+
newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
|
|
7957
|
+
}
|
|
7958
|
+
else {
|
|
7959
|
+
newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
|
|
7960
|
+
}
|
|
7961
|
+
// Intrusion check (mirrors trailingStop method: applied before TRAILING_STOP_LOSS_FN, for all calls)
|
|
7962
|
+
if (signal.position === "long" && currentPrice < newStopLoss)
|
|
7963
|
+
return false;
|
|
7964
|
+
if (signal.position === "short" && currentPrice > newStopLoss)
|
|
7965
|
+
return false;
|
|
7966
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
7967
|
+
if (signal.position === "long" && newStopLoss >= effectiveTakeProfit)
|
|
7968
|
+
return false;
|
|
7969
|
+
if (signal.position === "short" && newStopLoss <= effectiveTakeProfit)
|
|
7970
|
+
return false;
|
|
7971
|
+
// Absorption check (mirrors TRAILING_STOP_LOSS_FN: first call is unconditional)
|
|
7972
|
+
const currentTrailingSL = signal._trailingPriceStopLoss;
|
|
7973
|
+
if (currentTrailingSL !== undefined) {
|
|
7974
|
+
if (signal.position === "long" && newStopLoss <= currentTrailingSL)
|
|
7975
|
+
return false;
|
|
7976
|
+
if (signal.position === "short" && newStopLoss >= currentTrailingSL)
|
|
7977
|
+
return false;
|
|
7978
|
+
}
|
|
7979
|
+
return true;
|
|
7980
|
+
}
|
|
7754
7981
|
/**
|
|
7755
7982
|
* Adjusts trailing stop-loss by shifting distance between entry and original SL.
|
|
7756
7983
|
*
|
|
@@ -7939,6 +8166,65 @@ class ClientStrategy {
|
|
|
7939
8166
|
});
|
|
7940
8167
|
return true;
|
|
7941
8168
|
}
|
|
8169
|
+
/**
|
|
8170
|
+
* Validates preconditions for trailingTake without mutating state.
|
|
8171
|
+
*
|
|
8172
|
+
* Returns false (never throws) when any condition would cause trailingTake to fail or skip.
|
|
8173
|
+
* Includes absorption check: returns false if new TP would not improve on the current trailing TP.
|
|
8174
|
+
*
|
|
8175
|
+
* @param symbol - Trading pair symbol
|
|
8176
|
+
* @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
|
|
8177
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
8178
|
+
* @returns boolean - true if trailingTake would execute, false otherwise
|
|
8179
|
+
*/
|
|
8180
|
+
async validateTrailingTake(symbol, percentShift, currentPrice) {
|
|
8181
|
+
this.params.logger.debug("ClientStrategy validateTrailingTake", {
|
|
8182
|
+
symbol,
|
|
8183
|
+
percentShift,
|
|
8184
|
+
currentPrice,
|
|
8185
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
8186
|
+
});
|
|
8187
|
+
if (!this._pendingSignal)
|
|
8188
|
+
return false;
|
|
8189
|
+
if (typeof percentShift !== "number" || !isFinite(percentShift))
|
|
8190
|
+
return false;
|
|
8191
|
+
if (percentShift < -100 || percentShift > 100)
|
|
8192
|
+
return false;
|
|
8193
|
+
if (percentShift === 0)
|
|
8194
|
+
return false;
|
|
8195
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
8196
|
+
return false;
|
|
8197
|
+
const signal = this._pendingSignal;
|
|
8198
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
8199
|
+
const tpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
8200
|
+
const newTpDistancePercent = tpDistancePercent + percentShift;
|
|
8201
|
+
let newTakeProfit;
|
|
8202
|
+
if (signal.position === "long") {
|
|
8203
|
+
newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
|
|
8204
|
+
}
|
|
8205
|
+
else {
|
|
8206
|
+
newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
|
|
8207
|
+
}
|
|
8208
|
+
// Intrusion check (mirrors trailingTake method: applied before TRAILING_TAKE_PROFIT_FN, for all calls)
|
|
8209
|
+
if (signal.position === "long" && currentPrice > newTakeProfit)
|
|
8210
|
+
return false;
|
|
8211
|
+
if (signal.position === "short" && currentPrice < newTakeProfit)
|
|
8212
|
+
return false;
|
|
8213
|
+
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
8214
|
+
if (signal.position === "long" && newTakeProfit <= effectiveStopLoss)
|
|
8215
|
+
return false;
|
|
8216
|
+
if (signal.position === "short" && newTakeProfit >= effectiveStopLoss)
|
|
8217
|
+
return false;
|
|
8218
|
+
// Absorption check (mirrors TRAILING_TAKE_PROFIT_FN: first call is unconditional)
|
|
8219
|
+
const currentTrailingTP = signal._trailingPriceTakeProfit;
|
|
8220
|
+
if (currentTrailingTP !== undefined) {
|
|
8221
|
+
if (signal.position === "long" && newTakeProfit >= currentTrailingTP)
|
|
8222
|
+
return false;
|
|
8223
|
+
if (signal.position === "short" && newTakeProfit <= currentTrailingTP)
|
|
8224
|
+
return false;
|
|
8225
|
+
}
|
|
8226
|
+
return true;
|
|
8227
|
+
}
|
|
7942
8228
|
/**
|
|
7943
8229
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
7944
8230
|
*
|
|
@@ -8113,6 +8399,41 @@ class ClientStrategy {
|
|
|
8113
8399
|
});
|
|
8114
8400
|
return true;
|
|
8115
8401
|
}
|
|
8402
|
+
/**
|
|
8403
|
+
* Validates preconditions for averageBuy without mutating state.
|
|
8404
|
+
*
|
|
8405
|
+
* Returns false (never throws) when any condition would cause averageBuy to fail or skip.
|
|
8406
|
+
*
|
|
8407
|
+
* @param symbol - Trading pair symbol
|
|
8408
|
+
* @param currentPrice - New entry price to add
|
|
8409
|
+
* @returns boolean - true if averageBuy would execute, false otherwise
|
|
8410
|
+
*/
|
|
8411
|
+
async validateAverageBuy(symbol, currentPrice) {
|
|
8412
|
+
this.params.logger.debug("ClientStrategy validateAverageBuy", {
|
|
8413
|
+
symbol,
|
|
8414
|
+
currentPrice,
|
|
8415
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
8416
|
+
});
|
|
8417
|
+
if (!this._pendingSignal)
|
|
8418
|
+
return false;
|
|
8419
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
8420
|
+
return false;
|
|
8421
|
+
const signal = this._pendingSignal;
|
|
8422
|
+
const entries = (!signal._entry || signal._entry.length === 0)
|
|
8423
|
+
? [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST }]
|
|
8424
|
+
: signal._entry;
|
|
8425
|
+
if (signal.position === "long") {
|
|
8426
|
+
const minEntryPrice = Math.min(...entries.map((e) => e.price));
|
|
8427
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_DCA_EVERYWHERE && currentPrice >= minEntryPrice)
|
|
8428
|
+
return false;
|
|
8429
|
+
}
|
|
8430
|
+
else {
|
|
8431
|
+
const maxEntryPrice = Math.max(...entries.map((e) => e.price));
|
|
8432
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_DCA_EVERYWHERE && currentPrice <= maxEntryPrice)
|
|
8433
|
+
return false;
|
|
8434
|
+
}
|
|
8435
|
+
return true;
|
|
8436
|
+
}
|
|
8116
8437
|
/**
|
|
8117
8438
|
* Adds a new averaging entry to an open position (DCA — Dollar Cost Averaging).
|
|
8118
8439
|
*
|
|
@@ -8498,12 +8819,22 @@ const Risk = new RiskUtils();
|
|
|
8498
8819
|
*/
|
|
8499
8820
|
const CREATE_SYNC_FN = (self, strategyName, exchangeName, frameName, backtest) => trycatch(async (event) => {
|
|
8500
8821
|
if (event.backtest) {
|
|
8501
|
-
return;
|
|
8822
|
+
return true;
|
|
8502
8823
|
}
|
|
8503
8824
|
await syncSubject.next(event);
|
|
8504
8825
|
await self.actionCoreService.signalSync(backtest, event, { strategyName, exchangeName, frameName });
|
|
8505
8826
|
return true;
|
|
8506
8827
|
}, {
|
|
8828
|
+
fallback: (error) => {
|
|
8829
|
+
const message = "StrategyConnectionService CREATE_SYNC_FN thrown. Broker rejected order request";
|
|
8830
|
+
const payload = {
|
|
8831
|
+
error: errorData(error),
|
|
8832
|
+
message: getErrorMessage(error),
|
|
8833
|
+
};
|
|
8834
|
+
self.loggerService.warn(message, payload);
|
|
8835
|
+
console.error(message, payload);
|
|
8836
|
+
errorEmitter.next(error);
|
|
8837
|
+
},
|
|
8507
8838
|
defaultValue: false,
|
|
8508
8839
|
});
|
|
8509
8840
|
/**
|
|
@@ -9204,6 +9535,21 @@ class StrategyConnectionService {
|
|
|
9204
9535
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9205
9536
|
await strategy.closePending(symbol, backtest, closeId);
|
|
9206
9537
|
};
|
|
9538
|
+
/**
|
|
9539
|
+
* Checks whether `partialProfit` would succeed without executing it.
|
|
9540
|
+
* Delegates to `ClientStrategy.validatePartialProfit()` — no throws, pure boolean result.
|
|
9541
|
+
*
|
|
9542
|
+
* @param backtest - Whether running in backtest mode
|
|
9543
|
+
* @param symbol - Trading pair symbol
|
|
9544
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
9545
|
+
* @param currentPrice - Current market price to validate against
|
|
9546
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9547
|
+
* @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
|
|
9548
|
+
*/
|
|
9549
|
+
this.validatePartialProfit = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9550
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9551
|
+
return Promise.resolve(strategy.validatePartialProfit(symbol, percentToClose, currentPrice));
|
|
9552
|
+
};
|
|
9207
9553
|
/**
|
|
9208
9554
|
* Executes partial close at profit level (moving toward TP).
|
|
9209
9555
|
*
|
|
@@ -9245,6 +9591,21 @@ class StrategyConnectionService {
|
|
|
9245
9591
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9246
9592
|
return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest);
|
|
9247
9593
|
};
|
|
9594
|
+
/**
|
|
9595
|
+
* Checks whether `partialLoss` would succeed without executing it.
|
|
9596
|
+
* Delegates to `ClientStrategy.validatePartialLoss()` — no throws, pure boolean result.
|
|
9597
|
+
*
|
|
9598
|
+
* @param backtest - Whether running in backtest mode
|
|
9599
|
+
* @param symbol - Trading pair symbol
|
|
9600
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
9601
|
+
* @param currentPrice - Current market price to validate against
|
|
9602
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9603
|
+
* @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
|
|
9604
|
+
*/
|
|
9605
|
+
this.validatePartialLoss = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9606
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9607
|
+
return Promise.resolve(strategy.validatePartialLoss(symbol, percentToClose, currentPrice));
|
|
9608
|
+
};
|
|
9248
9609
|
/**
|
|
9249
9610
|
* Executes partial close at loss level (moving toward SL).
|
|
9250
9611
|
*
|
|
@@ -9286,6 +9647,21 @@ class StrategyConnectionService {
|
|
|
9286
9647
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9287
9648
|
return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest);
|
|
9288
9649
|
};
|
|
9650
|
+
/**
|
|
9651
|
+
* Checks whether `trailingStop` would succeed without executing it.
|
|
9652
|
+
* Delegates to `ClientStrategy.validateTrailingStop()` — no throws, pure boolean result.
|
|
9653
|
+
*
|
|
9654
|
+
* @param backtest - Whether running in backtest mode
|
|
9655
|
+
* @param symbol - Trading pair symbol
|
|
9656
|
+
* @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
|
|
9657
|
+
* @param currentPrice - Current market price to validate against
|
|
9658
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9659
|
+
* @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
|
|
9660
|
+
*/
|
|
9661
|
+
this.validateTrailingStop = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9662
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9663
|
+
return Promise.resolve(strategy.validateTrailingStop(symbol, percentShift, currentPrice));
|
|
9664
|
+
};
|
|
9289
9665
|
/**
|
|
9290
9666
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
9291
9667
|
*
|
|
@@ -9325,6 +9701,21 @@ class StrategyConnectionService {
|
|
|
9325
9701
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9326
9702
|
return await strategy.trailingStop(symbol, percentShift, currentPrice, backtest);
|
|
9327
9703
|
};
|
|
9704
|
+
/**
|
|
9705
|
+
* Checks whether `trailingTake` would succeed without executing it.
|
|
9706
|
+
* Delegates to `ClientStrategy.validateTrailingTake()` — no throws, pure boolean result.
|
|
9707
|
+
*
|
|
9708
|
+
* @param backtest - Whether running in backtest mode
|
|
9709
|
+
* @param symbol - Trading pair symbol
|
|
9710
|
+
* @param percentShift - Percentage adjustment to ORIGINAL TP distance [-100, 100], excluding 0
|
|
9711
|
+
* @param currentPrice - Current market price to validate against
|
|
9712
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9713
|
+
* @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
|
|
9714
|
+
*/
|
|
9715
|
+
this.validateTrailingTake = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9716
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9717
|
+
return Promise.resolve(strategy.validateTrailingTake(symbol, percentShift, currentPrice));
|
|
9718
|
+
};
|
|
9328
9719
|
/**
|
|
9329
9720
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
9330
9721
|
*
|
|
@@ -9364,6 +9755,20 @@ class StrategyConnectionService {
|
|
|
9364
9755
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9365
9756
|
return await strategy.trailingTake(symbol, percentShift, currentPrice, backtest);
|
|
9366
9757
|
};
|
|
9758
|
+
/**
|
|
9759
|
+
* Checks whether `breakeven` would succeed without executing it.
|
|
9760
|
+
* Delegates to `ClientStrategy.validateBreakeven()` — no throws, pure boolean result.
|
|
9761
|
+
*
|
|
9762
|
+
* @param backtest - Whether running in backtest mode
|
|
9763
|
+
* @param symbol - Trading pair symbol
|
|
9764
|
+
* @param currentPrice - Current market price to validate against
|
|
9765
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9766
|
+
* @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
|
|
9767
|
+
*/
|
|
9768
|
+
this.validateBreakeven = (backtest, symbol, currentPrice, context) => {
|
|
9769
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9770
|
+
return Promise.resolve(strategy.validateBreakeven(symbol, currentPrice));
|
|
9771
|
+
};
|
|
9367
9772
|
/**
|
|
9368
9773
|
* Delegates to ClientStrategy.breakeven() with current execution context.
|
|
9369
9774
|
*
|
|
@@ -9429,6 +9834,20 @@ class StrategyConnectionService {
|
|
|
9429
9834
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9430
9835
|
return await strategy.activateScheduled(symbol, backtest, activateId);
|
|
9431
9836
|
};
|
|
9837
|
+
/**
|
|
9838
|
+
* Checks whether `averageBuy` would succeed without executing it.
|
|
9839
|
+
* Delegates to `ClientStrategy.validateAverageBuy()` — no throws, pure boolean result.
|
|
9840
|
+
*
|
|
9841
|
+
* @param backtest - Whether running in backtest mode
|
|
9842
|
+
* @param symbol - Trading pair symbol
|
|
9843
|
+
* @param currentPrice - New entry price to validate
|
|
9844
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9845
|
+
* @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
|
|
9846
|
+
*/
|
|
9847
|
+
this.validateAverageBuy = (backtest, symbol, currentPrice, context) => {
|
|
9848
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9849
|
+
return Promise.resolve(strategy.validateAverageBuy(symbol, currentPrice));
|
|
9850
|
+
};
|
|
9432
9851
|
/**
|
|
9433
9852
|
* Adds a new DCA entry to the active pending signal.
|
|
9434
9853
|
*
|
|
@@ -12787,6 +13206,28 @@ class StrategyCoreService {
|
|
|
12787
13206
|
}
|
|
12788
13207
|
return await this.strategyConnectionService.clear(payload);
|
|
12789
13208
|
};
|
|
13209
|
+
/**
|
|
13210
|
+
* Checks whether `partialProfit` would succeed without executing it.
|
|
13211
|
+
* Validates context, then delegates to StrategyConnectionService.validatePartialProfit().
|
|
13212
|
+
*
|
|
13213
|
+
* @param backtest - Whether running in backtest mode
|
|
13214
|
+
* @param symbol - Trading pair symbol
|
|
13215
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
13216
|
+
* @param currentPrice - Current market price to validate against
|
|
13217
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13218
|
+
* @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
|
|
13219
|
+
*/
|
|
13220
|
+
this.validatePartialProfit = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
13221
|
+
this.loggerService.log("strategyCoreService validatePartialProfit", {
|
|
13222
|
+
symbol,
|
|
13223
|
+
percentToClose,
|
|
13224
|
+
currentPrice,
|
|
13225
|
+
context,
|
|
13226
|
+
backtest,
|
|
13227
|
+
});
|
|
13228
|
+
await this.validate(context);
|
|
13229
|
+
return await this.strategyConnectionService.validatePartialProfit(backtest, symbol, percentToClose, currentPrice, context);
|
|
13230
|
+
};
|
|
12790
13231
|
/**
|
|
12791
13232
|
* Executes partial close at profit level (moving toward TP).
|
|
12792
13233
|
*
|
|
@@ -12828,6 +13269,28 @@ class StrategyCoreService {
|
|
|
12828
13269
|
await this.validate(context);
|
|
12829
13270
|
return await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
|
|
12830
13271
|
};
|
|
13272
|
+
/**
|
|
13273
|
+
* Checks whether `partialLoss` would succeed without executing it.
|
|
13274
|
+
* Validates context, then delegates to StrategyConnectionService.validatePartialLoss().
|
|
13275
|
+
*
|
|
13276
|
+
* @param backtest - Whether running in backtest mode
|
|
13277
|
+
* @param symbol - Trading pair symbol
|
|
13278
|
+
* @param percentToClose - Percentage of position to check (0-100]
|
|
13279
|
+
* @param currentPrice - Current market price to validate against
|
|
13280
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13281
|
+
* @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
|
|
13282
|
+
*/
|
|
13283
|
+
this.validatePartialLoss = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
13284
|
+
this.loggerService.log("strategyCoreService validatePartialLoss", {
|
|
13285
|
+
symbol,
|
|
13286
|
+
percentToClose,
|
|
13287
|
+
currentPrice,
|
|
13288
|
+
context,
|
|
13289
|
+
backtest,
|
|
13290
|
+
});
|
|
13291
|
+
await this.validate(context);
|
|
13292
|
+
return await this.strategyConnectionService.validatePartialLoss(backtest, symbol, percentToClose, currentPrice, context);
|
|
13293
|
+
};
|
|
12831
13294
|
/**
|
|
12832
13295
|
* Executes partial close at loss level (moving toward SL).
|
|
12833
13296
|
*
|
|
@@ -12897,6 +13360,28 @@ class StrategyCoreService {
|
|
|
12897
13360
|
* );
|
|
12898
13361
|
* ```
|
|
12899
13362
|
*/
|
|
13363
|
+
this.validateTrailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
13364
|
+
this.loggerService.log("strategyCoreService validateTrailingStop", {
|
|
13365
|
+
symbol,
|
|
13366
|
+
percentShift,
|
|
13367
|
+
currentPrice,
|
|
13368
|
+
context,
|
|
13369
|
+
backtest,
|
|
13370
|
+
});
|
|
13371
|
+
await this.validate(context);
|
|
13372
|
+
return await this.strategyConnectionService.validateTrailingStop(backtest, symbol, percentShift, currentPrice, context);
|
|
13373
|
+
};
|
|
13374
|
+
/**
|
|
13375
|
+
* Checks whether `trailingStop` would succeed without executing it.
|
|
13376
|
+
* Validates context, then delegates to StrategyConnectionService.validateTrailingStop().
|
|
13377
|
+
*
|
|
13378
|
+
* @param backtest - Whether running in backtest mode
|
|
13379
|
+
* @param symbol - Trading pair symbol
|
|
13380
|
+
* @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
|
|
13381
|
+
* @param currentPrice - Current market price to validate against
|
|
13382
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13383
|
+
* @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
|
|
13384
|
+
*/
|
|
12900
13385
|
this.trailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
12901
13386
|
this.loggerService.log("strategyCoreService trailingStop", {
|
|
12902
13387
|
symbol,
|
|
@@ -12932,6 +13417,28 @@ class StrategyCoreService {
|
|
|
12932
13417
|
* );
|
|
12933
13418
|
* ```
|
|
12934
13419
|
*/
|
|
13420
|
+
this.validateTrailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
13421
|
+
this.loggerService.log("strategyCoreService validateTrailingTake", {
|
|
13422
|
+
symbol,
|
|
13423
|
+
percentShift,
|
|
13424
|
+
currentPrice,
|
|
13425
|
+
context,
|
|
13426
|
+
backtest,
|
|
13427
|
+
});
|
|
13428
|
+
await this.validate(context);
|
|
13429
|
+
return await this.strategyConnectionService.validateTrailingTake(backtest, symbol, percentShift, currentPrice, context);
|
|
13430
|
+
};
|
|
13431
|
+
/**
|
|
13432
|
+
* Checks whether `trailingTake` would succeed without executing it.
|
|
13433
|
+
* Validates context, then delegates to StrategyConnectionService.validateTrailingTake().
|
|
13434
|
+
*
|
|
13435
|
+
* @param backtest - Whether running in backtest mode
|
|
13436
|
+
* @param symbol - Trading pair symbol
|
|
13437
|
+
* @param percentShift - Percentage adjustment to ORIGINAL TP distance [-100, 100], excluding 0
|
|
13438
|
+
* @param currentPrice - Current market price to validate against
|
|
13439
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13440
|
+
* @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
|
|
13441
|
+
*/
|
|
12935
13442
|
this.trailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
12936
13443
|
this.loggerService.log("strategyCoreService trailingTake", {
|
|
12937
13444
|
symbol,
|
|
@@ -12963,6 +13470,26 @@ class StrategyCoreService {
|
|
|
12963
13470
|
* );
|
|
12964
13471
|
* ```
|
|
12965
13472
|
*/
|
|
13473
|
+
this.validateBreakeven = async (backtest, symbol, currentPrice, context) => {
|
|
13474
|
+
this.loggerService.log("strategyCoreService validateBreakeven", {
|
|
13475
|
+
symbol,
|
|
13476
|
+
currentPrice,
|
|
13477
|
+
context,
|
|
13478
|
+
backtest,
|
|
13479
|
+
});
|
|
13480
|
+
await this.validate(context);
|
|
13481
|
+
return await this.strategyConnectionService.validateBreakeven(backtest, symbol, currentPrice, context);
|
|
13482
|
+
};
|
|
13483
|
+
/**
|
|
13484
|
+
* Checks whether `breakeven` would succeed without executing it.
|
|
13485
|
+
* Validates context, then delegates to StrategyConnectionService.validateBreakeven().
|
|
13486
|
+
*
|
|
13487
|
+
* @param backtest - Whether running in backtest mode
|
|
13488
|
+
* @param symbol - Trading pair symbol
|
|
13489
|
+
* @param currentPrice - Current market price to validate against
|
|
13490
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13491
|
+
* @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
|
|
13492
|
+
*/
|
|
12966
13493
|
this.breakeven = async (backtest, symbol, currentPrice, context) => {
|
|
12967
13494
|
this.loggerService.log("strategyCoreService breakeven", {
|
|
12968
13495
|
symbol,
|
|
@@ -13018,6 +13545,26 @@ class StrategyCoreService {
|
|
|
13018
13545
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13019
13546
|
* @returns Promise<boolean> - true if entry added, false if rejected
|
|
13020
13547
|
*/
|
|
13548
|
+
this.validateAverageBuy = async (backtest, symbol, currentPrice, context) => {
|
|
13549
|
+
this.loggerService.log("strategyCoreService validateAverageBuy", {
|
|
13550
|
+
symbol,
|
|
13551
|
+
currentPrice,
|
|
13552
|
+
context,
|
|
13553
|
+
backtest,
|
|
13554
|
+
});
|
|
13555
|
+
await this.validate(context);
|
|
13556
|
+
return await this.strategyConnectionService.validateAverageBuy(backtest, symbol, currentPrice, context);
|
|
13557
|
+
};
|
|
13558
|
+
/**
|
|
13559
|
+
* Checks whether `averageBuy` would succeed without executing it.
|
|
13560
|
+
* Validates context, then delegates to StrategyConnectionService.validateAverageBuy().
|
|
13561
|
+
*
|
|
13562
|
+
* @param backtest - Whether running in backtest mode
|
|
13563
|
+
* @param symbol - Trading pair symbol
|
|
13564
|
+
* @param currentPrice - New entry price to validate
|
|
13565
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13566
|
+
* @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
|
|
13567
|
+
*/
|
|
13021
13568
|
this.averageBuy = async (backtest, symbol, currentPrice, context, cost) => {
|
|
13022
13569
|
this.loggerService.log("strategyCoreService averageBuy", {
|
|
13023
13570
|
symbol,
|
|
@@ -30137,6 +30684,1026 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
|
|
|
30137
30684
|
return (dollarAmount / investedCost) * 100;
|
|
30138
30685
|
};
|
|
30139
30686
|
|
|
30687
|
+
/**
|
|
30688
|
+
* Convert an absolute stop-loss price to a percentShift for `commitTrailingStop`.
|
|
30689
|
+
*
|
|
30690
|
+
* percentShift = newSlDistancePercent - originalSlDistancePercent
|
|
30691
|
+
* where distance = Math.abs((effectivePriceOpen - slPrice) / effectivePriceOpen * 100)
|
|
30692
|
+
*
|
|
30693
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
30694
|
+
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
30695
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30696
|
+
* @returns percentShift to pass to `commitTrailingStop`
|
|
30697
|
+
*
|
|
30698
|
+
* @example
|
|
30699
|
+
* // LONG: entry=100, originalSL=90, desired newSL=95
|
|
30700
|
+
* const shift = slPriceToPercentShift(95, 90, 100); // -5
|
|
30701
|
+
* await commitTrailingStop("BTCUSDT", shift, currentPrice);
|
|
30702
|
+
*/
|
|
30703
|
+
const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectivePriceOpen) => {
|
|
30704
|
+
const originalSlDistancePercent = Math.abs((effectivePriceOpen - originalStopLossPrice) / effectivePriceOpen * 100);
|
|
30705
|
+
const newSlDistancePercent = Math.abs((effectivePriceOpen - newStopLossPrice) / effectivePriceOpen * 100);
|
|
30706
|
+
return newSlDistancePercent - originalSlDistancePercent;
|
|
30707
|
+
};
|
|
30708
|
+
|
|
30709
|
+
/**
|
|
30710
|
+
* Convert an absolute take-profit price to a percentShift for `commitTrailingTake`.
|
|
30711
|
+
*
|
|
30712
|
+
* percentShift = newTpDistancePercent - originalTpDistancePercent
|
|
30713
|
+
* where distance = Math.abs((tpPrice - effectivePriceOpen) / effectivePriceOpen * 100)
|
|
30714
|
+
*
|
|
30715
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
30716
|
+
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
30717
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30718
|
+
* @returns percentShift to pass to `commitTrailingTake`
|
|
30719
|
+
*
|
|
30720
|
+
* @example
|
|
30721
|
+
* // LONG: entry=100, originalTP=110, desired newTP=107
|
|
30722
|
+
* const shift = tpPriceToPercentShift(107, 110, 100); // -3
|
|
30723
|
+
* await commitTrailingTake("BTCUSDT", shift, currentPrice);
|
|
30724
|
+
*/
|
|
30725
|
+
const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effectivePriceOpen) => {
|
|
30726
|
+
const originalTpDistancePercent = Math.abs((originalTakeProfitPrice - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
30727
|
+
const newTpDistancePercent = Math.abs((newTakeProfitPrice - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
30728
|
+
return newTpDistancePercent - originalTpDistancePercent;
|
|
30729
|
+
};
|
|
30730
|
+
|
|
30731
|
+
/**
|
|
30732
|
+
* Convert a percentShift for `commitTrailingStop` back to an absolute stop-loss price.
|
|
30733
|
+
*
|
|
30734
|
+
* Inverse of `slPriceToPercentShift`.
|
|
30735
|
+
*
|
|
30736
|
+
* newSlDistancePercent = originalSlDistancePercent + percentShift
|
|
30737
|
+
* LONG: newStopLossPrice = effectivePriceOpen * (1 - newSlDistancePercent / 100)
|
|
30738
|
+
* SHORT: newStopLossPrice = effectivePriceOpen * (1 + newSlDistancePercent / 100)
|
|
30739
|
+
*
|
|
30740
|
+
* @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
|
|
30741
|
+
* @param originalStopLossPrice - Original stop-loss price from the pending signal
|
|
30742
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30743
|
+
* @param position - Position direction: "long" or "short"
|
|
30744
|
+
* @returns Absolute stop-loss price corresponding to the given percentShift
|
|
30745
|
+
*
|
|
30746
|
+
* @example
|
|
30747
|
+
* // LONG: entry=100, originalSL=90, percentShift=-5
|
|
30748
|
+
* const price = slPercentShiftToPrice(-5, 90, 100, "long"); // 95
|
|
30749
|
+
*/
|
|
30750
|
+
const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePriceOpen, position) => {
|
|
30751
|
+
const originalSlDistancePercent = Math.abs((effectivePriceOpen - originalStopLossPrice) / effectivePriceOpen * 100);
|
|
30752
|
+
const newSlDistancePercent = originalSlDistancePercent + percentShift;
|
|
30753
|
+
return position === "long"
|
|
30754
|
+
? effectivePriceOpen * (1 - newSlDistancePercent / 100)
|
|
30755
|
+
: effectivePriceOpen * (1 + newSlDistancePercent / 100);
|
|
30756
|
+
};
|
|
30757
|
+
|
|
30758
|
+
/**
|
|
30759
|
+
* Convert a percentShift for `commitTrailingTake` back to an absolute take-profit price.
|
|
30760
|
+
*
|
|
30761
|
+
* Inverse of `tpPriceToPercentShift`.
|
|
30762
|
+
*
|
|
30763
|
+
* newTpDistancePercent = originalTpDistancePercent + percentShift
|
|
30764
|
+
* LONG: newTakeProfitPrice = effectivePriceOpen * (1 + newTpDistancePercent / 100)
|
|
30765
|
+
* SHORT: newTakeProfitPrice = effectivePriceOpen * (1 - newTpDistancePercent / 100)
|
|
30766
|
+
*
|
|
30767
|
+
* @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
|
|
30768
|
+
* @param originalTakeProfitPrice - Original take-profit price from the pending signal
|
|
30769
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30770
|
+
* @param position - Position direction: "long" or "short"
|
|
30771
|
+
* @returns Absolute take-profit price corresponding to the given percentShift
|
|
30772
|
+
*
|
|
30773
|
+
* @example
|
|
30774
|
+
* // LONG: entry=100, originalTP=110, percentShift=-3
|
|
30775
|
+
* const price = tpPercentShiftToPrice(-3, 110, 100, "long"); // 107
|
|
30776
|
+
*/
|
|
30777
|
+
const tpPercentShiftToPrice = (percentShift, originalTakeProfitPrice, effectivePriceOpen, position) => {
|
|
30778
|
+
const originalTpDistancePercent = Math.abs((originalTakeProfitPrice - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
30779
|
+
const newTpDistancePercent = originalTpDistancePercent + percentShift;
|
|
30780
|
+
return position === "long"
|
|
30781
|
+
? effectivePriceOpen * (1 + newTpDistancePercent / 100)
|
|
30782
|
+
: effectivePriceOpen * (1 - newTpDistancePercent / 100);
|
|
30783
|
+
};
|
|
30784
|
+
|
|
30785
|
+
/**
|
|
30786
|
+
* Compute the dollar cost of a partial close from percentToClose and current invested cost basis.
|
|
30787
|
+
*
|
|
30788
|
+
* cost = (percentToClose / 100) * investedCost
|
|
30789
|
+
*
|
|
30790
|
+
* @param percentToClose - Percentage of position to close (0–100)
|
|
30791
|
+
* @param investedCost - Current invested cost basis (from `getPositionInvestedCost`)
|
|
30792
|
+
* @returns Dollar amount that will be closed
|
|
30793
|
+
*
|
|
30794
|
+
* @example
|
|
30795
|
+
* // Position investedCost=$1000, closing 25%
|
|
30796
|
+
* const cost = percentToCloseCost(25, 1000); // 250
|
|
30797
|
+
*/
|
|
30798
|
+
const percentToCloseCost = (percentToClose, investedCost) => {
|
|
30799
|
+
return (percentToClose / 100) * investedCost;
|
|
30800
|
+
};
|
|
30801
|
+
|
|
30802
|
+
/**
|
|
30803
|
+
* Compute the new stop-loss price for a breakeven operation.
|
|
30804
|
+
*
|
|
30805
|
+
* Breakeven moves the SL to the effective entry price (effectivePriceOpen).
|
|
30806
|
+
* The value is the same regardless of position direction.
|
|
30807
|
+
*
|
|
30808
|
+
* @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
|
|
30809
|
+
* @returns New stop-loss price equal to the effective entry price
|
|
30810
|
+
*
|
|
30811
|
+
* @example
|
|
30812
|
+
* // LONG: entry=100, SL was 90 → breakeven SL = 100
|
|
30813
|
+
* const newSL = breakevenNewStopLossPrice(100); // 100
|
|
30814
|
+
*
|
|
30815
|
+
* // SHORT: entry=100, SL was 110 → breakeven SL = 100
|
|
30816
|
+
* const newSL = breakevenNewStopLossPrice(100); // 100
|
|
30817
|
+
*/
|
|
30818
|
+
const breakevenNewStopLossPrice = (effectivePriceOpen) => {
|
|
30819
|
+
return effectivePriceOpen;
|
|
30820
|
+
};
|
|
30821
|
+
|
|
30822
|
+
/**
|
|
30823
|
+
* Compute the effective take-profit price for a breakeven operation.
|
|
30824
|
+
*
|
|
30825
|
+
* Breakeven does not change the take-profit. Returns the currently effective TP:
|
|
30826
|
+
* `_trailingPriceTakeProfit` if set (trailing TP active), otherwise `priceTakeProfit`.
|
|
30827
|
+
*
|
|
30828
|
+
* @param priceTakeProfit - Original take-profit price from the pending signal
|
|
30829
|
+
* @param trailingPriceTakeProfit - Trailing take-profit override, or undefined if not set
|
|
30830
|
+
* @returns Effective take-profit price (unchanged by breakeven)
|
|
30831
|
+
*
|
|
30832
|
+
* @example
|
|
30833
|
+
* // No trailing TP set
|
|
30834
|
+
* const newTP = breakevenNewTakeProfitPrice(110, undefined); // 110
|
|
30835
|
+
*
|
|
30836
|
+
* // Trailing TP active
|
|
30837
|
+
* const newTP = breakevenNewTakeProfitPrice(110, 107); // 107
|
|
30838
|
+
*/
|
|
30839
|
+
const breakevenNewTakeProfitPrice = (priceTakeProfit, trailingPriceTakeProfit) => {
|
|
30840
|
+
return trailingPriceTakeProfit ?? priceTakeProfit;
|
|
30841
|
+
};
|
|
30842
|
+
|
|
30843
|
+
const BROKER_METHOD_NAME_COMMIT_SIGNAL_OPEN = "BrokerAdapter.commitSignalOpen";
|
|
30844
|
+
const BROKER_METHOD_NAME_COMMIT_SIGNAL_CLOSE = "BrokerAdapter.commitSignalClose";
|
|
30845
|
+
const BROKER_METHOD_NAME_COMMIT_PARTIAL_PROFIT = "BrokerAdapter.commitPartialProfit";
|
|
30846
|
+
const BROKER_METHOD_NAME_COMMIT_PARTIAL_LOSS = "BrokerAdapter.commitPartialLoss";
|
|
30847
|
+
const BROKER_METHOD_NAME_COMMIT_TRAILING_STOP = "BrokerAdapter.commitTrailingStop";
|
|
30848
|
+
const BROKER_METHOD_NAME_COMMIT_TRAILING_TAKE = "BrokerAdapter.commitTrailingTake";
|
|
30849
|
+
const BROKER_METHOD_NAME_COMMIT_BREAKEVEN = "BrokerAdapter.commitBreakeven";
|
|
30850
|
+
const BROKER_METHOD_NAME_COMMIT_AVERAGE_BUY = "BrokerAdapter.commitAverageBuy";
|
|
30851
|
+
const BROKER_METHOD_NAME_USE_BROKER_ADAPTER = "BrokerAdapter.useBrokerAdapter";
|
|
30852
|
+
const BROKER_METHOD_NAME_ENABLE = "BrokerAdapter.enable";
|
|
30853
|
+
const BROKER_METHOD_NAME_DISABLE = "BrokerAdapter.disable";
|
|
30854
|
+
const BROKER_BASE_METHOD_NAME_WAIT_FOR_INIT = "BrokerBase.waitForInit";
|
|
30855
|
+
const BROKER_BASE_METHOD_NAME_ON_SIGNAL_OPEN = "BrokerBase.onSignalOpenCommit";
|
|
30856
|
+
const BROKER_BASE_METHOD_NAME_ON_SIGNAL_CLOSE = "BrokerBase.onSignalCloseCommit";
|
|
30857
|
+
const BROKER_BASE_METHOD_NAME_ON_PARTIAL_PROFIT = "BrokerBase.onPartialProfitCommit";
|
|
30858
|
+
const BROKER_BASE_METHOD_NAME_ON_PARTIAL_LOSS = "BrokerBase.onPartialLossCommit";
|
|
30859
|
+
const BROKER_BASE_METHOD_NAME_ON_TRAILING_STOP = "BrokerBase.onTrailingStopCommit";
|
|
30860
|
+
const BROKER_BASE_METHOD_NAME_ON_TRAILING_TAKE = "BrokerBase.onTrailingTakeCommit";
|
|
30861
|
+
const BROKER_BASE_METHOD_NAME_ON_BREAKEVEN = "BrokerBase.onBreakevenCommit";
|
|
30862
|
+
const BROKER_BASE_METHOD_NAME_ON_AVERAGE_BUY = "BrokerBase.onAverageBuyCommit";
|
|
30863
|
+
class BrokerProxy {
|
|
30864
|
+
constructor(_instance) {
|
|
30865
|
+
this._instance = _instance;
|
|
30866
|
+
this.waitForInit = singleshot(async () => {
|
|
30867
|
+
if (this._instance.waitForInit) {
|
|
30868
|
+
await this._instance.waitForInit();
|
|
30869
|
+
}
|
|
30870
|
+
});
|
|
30871
|
+
}
|
|
30872
|
+
async onSignalOpenCommit(payload) {
|
|
30873
|
+
if (this._instance.onSignalOpenCommit) {
|
|
30874
|
+
await this._instance.onSignalOpenCommit(payload);
|
|
30875
|
+
}
|
|
30876
|
+
}
|
|
30877
|
+
async onSignalCloseCommit(payload) {
|
|
30878
|
+
if (this._instance.onSignalCloseCommit) {
|
|
30879
|
+
await this._instance.onSignalCloseCommit(payload);
|
|
30880
|
+
}
|
|
30881
|
+
}
|
|
30882
|
+
async onPartialProfitCommit(payload) {
|
|
30883
|
+
if (this._instance.onPartialProfitCommit) {
|
|
30884
|
+
await this._instance.onPartialProfitCommit(payload);
|
|
30885
|
+
}
|
|
30886
|
+
}
|
|
30887
|
+
async onPartialLossCommit(payload) {
|
|
30888
|
+
if (this._instance.onPartialLossCommit) {
|
|
30889
|
+
await this._instance.onPartialLossCommit(payload);
|
|
30890
|
+
}
|
|
30891
|
+
}
|
|
30892
|
+
async onTrailingStopCommit(payload) {
|
|
30893
|
+
if (this._instance.onTrailingStopCommit) {
|
|
30894
|
+
await this._instance.onTrailingStopCommit(payload);
|
|
30895
|
+
}
|
|
30896
|
+
}
|
|
30897
|
+
async onTrailingTakeCommit(payload) {
|
|
30898
|
+
if (this._instance.onTrailingTakeCommit) {
|
|
30899
|
+
await this._instance.onTrailingTakeCommit(payload);
|
|
30900
|
+
}
|
|
30901
|
+
}
|
|
30902
|
+
async onBreakevenCommit(payload) {
|
|
30903
|
+
if (this._instance.onBreakevenCommit) {
|
|
30904
|
+
await this._instance.onBreakevenCommit(payload);
|
|
30905
|
+
}
|
|
30906
|
+
}
|
|
30907
|
+
async onAverageBuyCommit(payload) {
|
|
30908
|
+
if (this._instance.onAverageBuyCommit) {
|
|
30909
|
+
await this._instance.onAverageBuyCommit(payload);
|
|
30910
|
+
}
|
|
30911
|
+
}
|
|
30912
|
+
}
|
|
30913
|
+
/**
|
|
30914
|
+
* Facade for broker integration — intercepts all commit* operations before DI-core mutations.
|
|
30915
|
+
*
|
|
30916
|
+
* Acts as a transaction control point: if any commit* method throws, the DI-core mutation
|
|
30917
|
+
* is never reached and the state remains unchanged.
|
|
30918
|
+
*
|
|
30919
|
+
* In backtest mode all commit* calls are silently skipped (payload.backtest === true).
|
|
30920
|
+
* In live mode the call is forwarded to the registered IBroker adapter via BrokerProxy.
|
|
30921
|
+
*
|
|
30922
|
+
* signal-open and signal-close events are routed automatically via syncSubject subscription
|
|
30923
|
+
* (activated on `enable()`). All other commit* methods are called explicitly from
|
|
30924
|
+
* Live.ts / Backtest.ts / strategy.ts before the corresponding strategyCoreService call.
|
|
30925
|
+
*
|
|
30926
|
+
* @example
|
|
30927
|
+
* ```typescript
|
|
30928
|
+
* import { Broker } from "backtest-kit";
|
|
30929
|
+
*
|
|
30930
|
+
* // Register a custom broker adapter
|
|
30931
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
30932
|
+
*
|
|
30933
|
+
* // Activate syncSubject subscription (signal-open / signal-close routing)
|
|
30934
|
+
* const dispose = Broker.enable();
|
|
30935
|
+
*
|
|
30936
|
+
* // ... run strategy ...
|
|
30937
|
+
*
|
|
30938
|
+
* // Deactivate when done
|
|
30939
|
+
* Broker.disable();
|
|
30940
|
+
* ```
|
|
30941
|
+
*/
|
|
30942
|
+
class BrokerAdapter {
|
|
30943
|
+
constructor() {
|
|
30944
|
+
this._brokerInstance = null;
|
|
30945
|
+
/**
|
|
30946
|
+
* Forwards a signal-open event to the registered broker adapter.
|
|
30947
|
+
*
|
|
30948
|
+
* Called automatically via syncSubject when `enable()` is active.
|
|
30949
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
30950
|
+
*
|
|
30951
|
+
* @param payload - Signal open details: symbol, cost, position, prices, context, backtest flag
|
|
30952
|
+
*
|
|
30953
|
+
* @example
|
|
30954
|
+
* ```typescript
|
|
30955
|
+
* await Broker.commitSignalOpen({
|
|
30956
|
+
* symbol: "BTCUSDT",
|
|
30957
|
+
* cost: 100,
|
|
30958
|
+
* position: "long",
|
|
30959
|
+
* priceOpen: 50000,
|
|
30960
|
+
* priceTakeProfit: 55000,
|
|
30961
|
+
* priceStopLoss: 48000,
|
|
30962
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
30963
|
+
* backtest: false,
|
|
30964
|
+
* });
|
|
30965
|
+
* ```
|
|
30966
|
+
*/
|
|
30967
|
+
this.commitSignalOpen = async (payload) => {
|
|
30968
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_SIGNAL_OPEN, {
|
|
30969
|
+
symbol: payload.symbol,
|
|
30970
|
+
context: payload.context,
|
|
30971
|
+
});
|
|
30972
|
+
if (!this.enable.hasValue()) {
|
|
30973
|
+
return;
|
|
30974
|
+
}
|
|
30975
|
+
if (payload.backtest) {
|
|
30976
|
+
return;
|
|
30977
|
+
}
|
|
30978
|
+
await this._brokerInstance?.onSignalOpenCommit(payload);
|
|
30979
|
+
};
|
|
30980
|
+
/**
|
|
30981
|
+
* Forwards a signal-close event to the registered broker adapter.
|
|
30982
|
+
*
|
|
30983
|
+
* Called automatically via syncSubject when `enable()` is active.
|
|
30984
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
30985
|
+
*
|
|
30986
|
+
* @param payload - Signal close details: symbol, cost, position, currentPrice, pnl, context, backtest flag
|
|
30987
|
+
*
|
|
30988
|
+
* @example
|
|
30989
|
+
* ```typescript
|
|
30990
|
+
* await Broker.commitSignalClose({
|
|
30991
|
+
* symbol: "BTCUSDT",
|
|
30992
|
+
* cost: 100,
|
|
30993
|
+
* position: "long",
|
|
30994
|
+
* currentPrice: 54000,
|
|
30995
|
+
* priceTakeProfit: 55000,
|
|
30996
|
+
* priceStopLoss: 48000,
|
|
30997
|
+
* totalEntries: 2,
|
|
30998
|
+
* totalPartials: 1,
|
|
30999
|
+
* pnl: { profit: 80, loss: 0, volume: 100 },
|
|
31000
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31001
|
+
* backtest: false,
|
|
31002
|
+
* });
|
|
31003
|
+
* ```
|
|
31004
|
+
*/
|
|
31005
|
+
this.commitSignalClose = async (payload) => {
|
|
31006
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_SIGNAL_CLOSE, {
|
|
31007
|
+
symbol: payload.symbol,
|
|
31008
|
+
context: payload.context,
|
|
31009
|
+
});
|
|
31010
|
+
if (!this.enable.hasValue()) {
|
|
31011
|
+
return;
|
|
31012
|
+
}
|
|
31013
|
+
if (payload.backtest) {
|
|
31014
|
+
return;
|
|
31015
|
+
}
|
|
31016
|
+
await this._brokerInstance?.onSignalCloseCommit(payload);
|
|
31017
|
+
};
|
|
31018
|
+
/**
|
|
31019
|
+
* Intercepts a partial-profit close before DI-core mutation.
|
|
31020
|
+
*
|
|
31021
|
+
* Called explicitly from Live.ts / Backtest.ts / strategy.ts after all validations pass,
|
|
31022
|
+
* but before `strategyCoreService.partialProfit()`. If this method throws, the DI mutation
|
|
31023
|
+
* is skipped and state remains unchanged.
|
|
31024
|
+
*
|
|
31025
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31026
|
+
*
|
|
31027
|
+
* @param payload - Partial profit details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest flag
|
|
31028
|
+
*
|
|
31029
|
+
* @example
|
|
31030
|
+
* ```typescript
|
|
31031
|
+
* await Broker.commitPartialProfit({
|
|
31032
|
+
* symbol: "BTCUSDT",
|
|
31033
|
+
* percentToClose: 30,
|
|
31034
|
+
* cost: 30,
|
|
31035
|
+
* currentPrice: 52000,
|
|
31036
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31037
|
+
* backtest: false,
|
|
31038
|
+
* });
|
|
31039
|
+
* ```
|
|
31040
|
+
*/
|
|
31041
|
+
this.commitPartialProfit = async (payload) => {
|
|
31042
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_PARTIAL_PROFIT, {
|
|
31043
|
+
symbol: payload.symbol,
|
|
31044
|
+
context: payload.context,
|
|
31045
|
+
});
|
|
31046
|
+
if (!this.enable.hasValue()) {
|
|
31047
|
+
return;
|
|
31048
|
+
}
|
|
31049
|
+
if (payload.backtest) {
|
|
31050
|
+
return;
|
|
31051
|
+
}
|
|
31052
|
+
await this._brokerInstance?.onPartialProfitCommit(payload);
|
|
31053
|
+
};
|
|
31054
|
+
/**
|
|
31055
|
+
* Intercepts a partial-loss close before DI-core mutation.
|
|
31056
|
+
*
|
|
31057
|
+
* Called explicitly from Live.ts / Backtest.ts / strategy.ts after all validations pass,
|
|
31058
|
+
* but before `strategyCoreService.partialLoss()`. If this method throws, the DI mutation
|
|
31059
|
+
* is skipped and state remains unchanged.
|
|
31060
|
+
*
|
|
31061
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31062
|
+
*
|
|
31063
|
+
* @param payload - Partial loss details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest flag
|
|
31064
|
+
*
|
|
31065
|
+
* @example
|
|
31066
|
+
* ```typescript
|
|
31067
|
+
* await Broker.commitPartialLoss({
|
|
31068
|
+
* symbol: "BTCUSDT",
|
|
31069
|
+
* percentToClose: 40,
|
|
31070
|
+
* cost: 40,
|
|
31071
|
+
* currentPrice: 48500,
|
|
31072
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31073
|
+
* backtest: false,
|
|
31074
|
+
* });
|
|
31075
|
+
* ```
|
|
31076
|
+
*/
|
|
31077
|
+
this.commitPartialLoss = async (payload) => {
|
|
31078
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_PARTIAL_LOSS, {
|
|
31079
|
+
symbol: payload.symbol,
|
|
31080
|
+
context: payload.context,
|
|
31081
|
+
});
|
|
31082
|
+
if (!this.enable.hasValue()) {
|
|
31083
|
+
return;
|
|
31084
|
+
}
|
|
31085
|
+
if (payload.backtest) {
|
|
31086
|
+
return;
|
|
31087
|
+
}
|
|
31088
|
+
await this._brokerInstance?.onPartialLossCommit(payload);
|
|
31089
|
+
};
|
|
31090
|
+
/**
|
|
31091
|
+
* Intercepts a trailing stop-loss update before DI-core mutation.
|
|
31092
|
+
*
|
|
31093
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.trailingStop()`.
|
|
31094
|
+
* `newStopLossPrice` is the absolute price computed from percentShift + original SL + effectivePriceOpen.
|
|
31095
|
+
*
|
|
31096
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31097
|
+
*
|
|
31098
|
+
* @param payload - Trailing stop details: symbol, percentShift, currentPrice, newStopLossPrice, context, backtest flag
|
|
31099
|
+
*
|
|
31100
|
+
* @example
|
|
31101
|
+
* ```typescript
|
|
31102
|
+
* // LONG: entry=100, originalSL=90, percentShift=-5 → newSL=95
|
|
31103
|
+
* await Broker.commitTrailingStop({
|
|
31104
|
+
* symbol: "BTCUSDT",
|
|
31105
|
+
* percentShift: -5,
|
|
31106
|
+
* currentPrice: 102,
|
|
31107
|
+
* newStopLossPrice: 95,
|
|
31108
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31109
|
+
* backtest: false,
|
|
31110
|
+
* });
|
|
31111
|
+
* ```
|
|
31112
|
+
*/
|
|
31113
|
+
this.commitTrailingStop = async (payload) => {
|
|
31114
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_TRAILING_STOP, {
|
|
31115
|
+
symbol: payload.symbol,
|
|
31116
|
+
context: payload.context,
|
|
31117
|
+
});
|
|
31118
|
+
if (!this.enable.hasValue()) {
|
|
31119
|
+
return;
|
|
31120
|
+
}
|
|
31121
|
+
if (payload.backtest) {
|
|
31122
|
+
return;
|
|
31123
|
+
}
|
|
31124
|
+
await this._brokerInstance?.onTrailingStopCommit(payload);
|
|
31125
|
+
};
|
|
31126
|
+
/**
|
|
31127
|
+
* Intercepts a trailing take-profit update before DI-core mutation.
|
|
31128
|
+
*
|
|
31129
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.trailingTake()`.
|
|
31130
|
+
* `newTakeProfitPrice` is the absolute price computed from percentShift + original TP + effectivePriceOpen.
|
|
31131
|
+
*
|
|
31132
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31133
|
+
*
|
|
31134
|
+
* @param payload - Trailing take details: symbol, percentShift, currentPrice, newTakeProfitPrice, context, backtest flag
|
|
31135
|
+
*
|
|
31136
|
+
* @example
|
|
31137
|
+
* ```typescript
|
|
31138
|
+
* // LONG: entry=100, originalTP=110, percentShift=-3 → newTP=107
|
|
31139
|
+
* await Broker.commitTrailingTake({
|
|
31140
|
+
* symbol: "BTCUSDT",
|
|
31141
|
+
* percentShift: -3,
|
|
31142
|
+
* currentPrice: 102,
|
|
31143
|
+
* newTakeProfitPrice: 107,
|
|
31144
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31145
|
+
* backtest: false,
|
|
31146
|
+
* });
|
|
31147
|
+
* ```
|
|
31148
|
+
*/
|
|
31149
|
+
this.commitTrailingTake = async (payload) => {
|
|
31150
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_TRAILING_TAKE, {
|
|
31151
|
+
symbol: payload.symbol,
|
|
31152
|
+
context: payload.context,
|
|
31153
|
+
});
|
|
31154
|
+
if (!this.enable.hasValue()) {
|
|
31155
|
+
return;
|
|
31156
|
+
}
|
|
31157
|
+
if (payload.backtest) {
|
|
31158
|
+
return;
|
|
31159
|
+
}
|
|
31160
|
+
await this._brokerInstance?.onTrailingTakeCommit(payload);
|
|
31161
|
+
};
|
|
31162
|
+
/**
|
|
31163
|
+
* Intercepts a breakeven operation before DI-core mutation.
|
|
31164
|
+
*
|
|
31165
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.breakeven()`.
|
|
31166
|
+
* `newStopLossPrice` equals effectivePriceOpen (entry price).
|
|
31167
|
+
* `newTakeProfitPrice` equals `_trailingPriceTakeProfit ?? priceTakeProfit` (TP is unchanged by breakeven).
|
|
31168
|
+
*
|
|
31169
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31170
|
+
*
|
|
31171
|
+
* @param payload - Breakeven details: symbol, currentPrice, newStopLossPrice, newTakeProfitPrice, context, backtest flag
|
|
31172
|
+
*
|
|
31173
|
+
* @example
|
|
31174
|
+
* ```typescript
|
|
31175
|
+
* // LONG: entry=100, currentPrice=100.5, newSL=100 (entry), newTP=110 (unchanged)
|
|
31176
|
+
* await Broker.commitBreakeven({
|
|
31177
|
+
* symbol: "BTCUSDT",
|
|
31178
|
+
* currentPrice: 100.5,
|
|
31179
|
+
* newStopLossPrice: 100,
|
|
31180
|
+
* newTakeProfitPrice: 110,
|
|
31181
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31182
|
+
* backtest: false,
|
|
31183
|
+
* });
|
|
31184
|
+
* ```
|
|
31185
|
+
*/
|
|
31186
|
+
this.commitBreakeven = async (payload) => {
|
|
31187
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_BREAKEVEN, {
|
|
31188
|
+
symbol: payload.symbol,
|
|
31189
|
+
context: payload.context,
|
|
31190
|
+
});
|
|
31191
|
+
if (!this.enable.hasValue()) {
|
|
31192
|
+
return;
|
|
31193
|
+
}
|
|
31194
|
+
if (payload.backtest) {
|
|
31195
|
+
return;
|
|
31196
|
+
}
|
|
31197
|
+
await this._brokerInstance?.onBreakevenCommit(payload);
|
|
31198
|
+
};
|
|
31199
|
+
/**
|
|
31200
|
+
* Intercepts a DCA average-buy entry before DI-core mutation.
|
|
31201
|
+
*
|
|
31202
|
+
* Called explicitly after all validations pass, but before `strategyCoreService.averageBuy()`.
|
|
31203
|
+
* `currentPrice` is the market price at which the new DCA entry is added.
|
|
31204
|
+
* `cost` is the dollar amount of the new entry (default: CC_POSITION_ENTRY_COST).
|
|
31205
|
+
*
|
|
31206
|
+
* Skipped silently in backtest mode or when no adapter is registered.
|
|
31207
|
+
*
|
|
31208
|
+
* @param payload - Average buy details: symbol, currentPrice, cost, context, backtest flag
|
|
31209
|
+
*
|
|
31210
|
+
* @example
|
|
31211
|
+
* ```typescript
|
|
31212
|
+
* // Add DCA entry at current market price
|
|
31213
|
+
* await Broker.commitAverageBuy({
|
|
31214
|
+
* symbol: "BTCUSDT",
|
|
31215
|
+
* currentPrice: 42000,
|
|
31216
|
+
* cost: 100,
|
|
31217
|
+
* context: { strategyName: "my-strategy", exchangeName: "binance", frameName: "1h" },
|
|
31218
|
+
* backtest: false,
|
|
31219
|
+
* });
|
|
31220
|
+
* ```
|
|
31221
|
+
*/
|
|
31222
|
+
this.commitAverageBuy = async (payload) => {
|
|
31223
|
+
bt.loggerService.info(BROKER_METHOD_NAME_COMMIT_AVERAGE_BUY, {
|
|
31224
|
+
symbol: payload.symbol,
|
|
31225
|
+
context: payload.context,
|
|
31226
|
+
});
|
|
31227
|
+
if (!this.enable.hasValue()) {
|
|
31228
|
+
return;
|
|
31229
|
+
}
|
|
31230
|
+
if (payload.backtest) {
|
|
31231
|
+
return;
|
|
31232
|
+
}
|
|
31233
|
+
await this._brokerInstance?.onAverageBuyCommit(payload);
|
|
31234
|
+
};
|
|
31235
|
+
/**
|
|
31236
|
+
* Registers a broker adapter instance or constructor to receive commit* callbacks.
|
|
31237
|
+
*
|
|
31238
|
+
* Must be called before `enable()`. Accepts either a class constructor (called with `new`)
|
|
31239
|
+
* or an already-instantiated object implementing `Partial<IBroker>`.
|
|
31240
|
+
*
|
|
31241
|
+
* @param broker - IBroker constructor or instance
|
|
31242
|
+
*
|
|
31243
|
+
* @example
|
|
31244
|
+
* ```typescript
|
|
31245
|
+
* import { Broker } from "backtest-kit";
|
|
31246
|
+
*
|
|
31247
|
+
* // Register via constructor
|
|
31248
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31249
|
+
*
|
|
31250
|
+
* // Register via instance
|
|
31251
|
+
* Broker.useBrokerAdapter(new MyBrokerAdapter());
|
|
31252
|
+
* ```
|
|
31253
|
+
*/
|
|
31254
|
+
this.useBrokerAdapter = (broker) => {
|
|
31255
|
+
bt.loggerService.info(BROKER_METHOD_NAME_USE_BROKER_ADAPTER, {});
|
|
31256
|
+
if (typeof broker === "function") {
|
|
31257
|
+
const instance = Reflect.construct(broker, []);
|
|
31258
|
+
this._brokerInstance = new BrokerProxy(instance);
|
|
31259
|
+
return;
|
|
31260
|
+
}
|
|
31261
|
+
this._brokerInstance = new BrokerProxy(broker);
|
|
31262
|
+
};
|
|
31263
|
+
/**
|
|
31264
|
+
* Activates the broker: subscribes to syncSubject for signal-open / signal-close routing.
|
|
31265
|
+
*
|
|
31266
|
+
* Must be called after `useBrokerAdapter()`. Returns a dispose function that unsubscribes
|
|
31267
|
+
* from syncSubject (equivalent to calling `disable()`).
|
|
31268
|
+
*
|
|
31269
|
+
* Calling `enable()` without a registered adapter throws immediately.
|
|
31270
|
+
* Calling `enable()` more than once is idempotent (singleshot guard).
|
|
31271
|
+
*
|
|
31272
|
+
* @returns Dispose function — call it to deactivate the broker subscription
|
|
31273
|
+
*
|
|
31274
|
+
* @example
|
|
31275
|
+
* ```typescript
|
|
31276
|
+
* import { Broker } from "backtest-kit";
|
|
31277
|
+
*
|
|
31278
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31279
|
+
* const dispose = Broker.enable();
|
|
31280
|
+
*
|
|
31281
|
+
* // ... run backtest or live session ...
|
|
31282
|
+
*
|
|
31283
|
+
* dispose(); // or Broker.disable()
|
|
31284
|
+
* ```
|
|
31285
|
+
*/
|
|
31286
|
+
this.enable = singleshot(() => {
|
|
31287
|
+
bt.loggerService.info(BROKER_METHOD_NAME_ENABLE, {});
|
|
31288
|
+
if (!this._brokerInstance) {
|
|
31289
|
+
this.enable.clear();
|
|
31290
|
+
throw new Error("No broker instance provided. Call Broker.useBrokerAdapter first.");
|
|
31291
|
+
}
|
|
31292
|
+
const unSignalOpen = syncSubject.subscribe(async (event) => {
|
|
31293
|
+
if (event.action !== "signal-open") {
|
|
31294
|
+
return;
|
|
31295
|
+
}
|
|
31296
|
+
await this.commitSignalOpen({
|
|
31297
|
+
position: event.signal.position,
|
|
31298
|
+
cost: event.signal.cost,
|
|
31299
|
+
symbol: event.symbol,
|
|
31300
|
+
priceTakeProfit: event.signal.priceTakeProfit,
|
|
31301
|
+
priceStopLoss: event.signal.priceStopLoss,
|
|
31302
|
+
priceOpen: event.signal.priceOpen,
|
|
31303
|
+
context: {
|
|
31304
|
+
strategyName: event.strategyName,
|
|
31305
|
+
exchangeName: event.exchangeName,
|
|
31306
|
+
frameName: event.frameName,
|
|
31307
|
+
},
|
|
31308
|
+
backtest: event.backtest,
|
|
31309
|
+
});
|
|
31310
|
+
});
|
|
31311
|
+
const unSignalClose = syncSubject.subscribe(async (event) => {
|
|
31312
|
+
if (event.action !== "signal-close") {
|
|
31313
|
+
return;
|
|
31314
|
+
}
|
|
31315
|
+
await this.commitSignalClose({
|
|
31316
|
+
position: event.signal.position,
|
|
31317
|
+
currentPrice: event.currentPrice,
|
|
31318
|
+
cost: event.signal.cost,
|
|
31319
|
+
symbol: event.symbol,
|
|
31320
|
+
pnl: event.pnl,
|
|
31321
|
+
totalEntries: event.totalEntries,
|
|
31322
|
+
totalPartials: event.totalPartials,
|
|
31323
|
+
priceStopLoss: event.signal.priceStopLoss,
|
|
31324
|
+
priceTakeProfit: event.signal.priceTakeProfit,
|
|
31325
|
+
context: {
|
|
31326
|
+
strategyName: event.strategyName,
|
|
31327
|
+
exchangeName: event.exchangeName,
|
|
31328
|
+
frameName: event.frameName,
|
|
31329
|
+
},
|
|
31330
|
+
backtest: event.backtest,
|
|
31331
|
+
});
|
|
31332
|
+
});
|
|
31333
|
+
const disposeFn = compose(() => unSignalOpen(), () => unSignalClose());
|
|
31334
|
+
return () => {
|
|
31335
|
+
this.enable.clear();
|
|
31336
|
+
disposeFn();
|
|
31337
|
+
};
|
|
31338
|
+
});
|
|
31339
|
+
/**
|
|
31340
|
+
* Deactivates the broker: unsubscribes from syncSubject and resets the singleshot guard.
|
|
31341
|
+
*
|
|
31342
|
+
* Idempotent — safe to call even if `enable()` was never called.
|
|
31343
|
+
* After `disable()`, `enable()` can be called again to reactivate.
|
|
31344
|
+
*
|
|
31345
|
+
* @example
|
|
31346
|
+
* ```typescript
|
|
31347
|
+
* import { Broker } from "backtest-kit";
|
|
31348
|
+
*
|
|
31349
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31350
|
+
* Broker.enable();
|
|
31351
|
+
*
|
|
31352
|
+
* // Stop receiving events
|
|
31353
|
+
* Broker.disable();
|
|
31354
|
+
* ```
|
|
31355
|
+
*/
|
|
31356
|
+
this.disable = () => {
|
|
31357
|
+
bt.loggerService.info(BROKER_METHOD_NAME_DISABLE, {});
|
|
31358
|
+
if (this.enable.hasValue()) {
|
|
31359
|
+
const lastSubscription = this.enable();
|
|
31360
|
+
lastSubscription();
|
|
31361
|
+
}
|
|
31362
|
+
};
|
|
31363
|
+
}
|
|
31364
|
+
}
|
|
31365
|
+
/**
|
|
31366
|
+
* Base class for custom broker adapter implementations.
|
|
31367
|
+
*
|
|
31368
|
+
* Provides default no-op implementations for all IBroker methods that log events.
|
|
31369
|
+
* Extend this class to implement a real exchange adapter for:
|
|
31370
|
+
* - Placing and canceling limit/market orders
|
|
31371
|
+
* - Updating stop-loss and take-profit levels on exchange
|
|
31372
|
+
* - Tracking position state in an external system
|
|
31373
|
+
* - Sending trade notifications (Telegram, Discord, Email)
|
|
31374
|
+
* - Recording trades to a database or analytics service
|
|
31375
|
+
*
|
|
31376
|
+
* Key features:
|
|
31377
|
+
* - All methods have default implementations (no need to override unused methods)
|
|
31378
|
+
* - Automatic logging of all events via bt.loggerService
|
|
31379
|
+
* - Implements the full IBroker interface
|
|
31380
|
+
* - `makeExtendable` applied for correct subclass instantiation
|
|
31381
|
+
*
|
|
31382
|
+
* Lifecycle:
|
|
31383
|
+
* 1. Constructor called (no arguments)
|
|
31384
|
+
* 2. `waitForInit()` called once for async initialization (e.g. exchange login)
|
|
31385
|
+
* 3. Event methods called as strategy executes
|
|
31386
|
+
* 4. No explicit dispose — clean up in `waitForInit` teardown or externally
|
|
31387
|
+
*
|
|
31388
|
+
* Event flow (called only in live mode, skipped in backtest):
|
|
31389
|
+
* - `onSignalOpenCommit` — new position opened
|
|
31390
|
+
* - `onSignalCloseCommit` — position closed (SL/TP hit or manual close)
|
|
31391
|
+
* - `onPartialProfitCommit` — partial close at profit executed
|
|
31392
|
+
* - `onPartialLossCommit` — partial close at loss executed
|
|
31393
|
+
* - `onTrailingStopCommit` — trailing stop-loss updated
|
|
31394
|
+
* - `onTrailingTakeCommit` — trailing take-profit updated
|
|
31395
|
+
* - `onBreakevenCommit` — stop-loss moved to entry price
|
|
31396
|
+
* - `onAverageBuyCommit` — new DCA entry added to position
|
|
31397
|
+
*
|
|
31398
|
+
* @example
|
|
31399
|
+
* ```typescript
|
|
31400
|
+
* import { BrokerBase, Broker } from "backtest-kit";
|
|
31401
|
+
*
|
|
31402
|
+
* // Extend BrokerBase and override only needed methods
|
|
31403
|
+
* class BinanceBroker extends BrokerBase {
|
|
31404
|
+
* private client: BinanceClient | null = null;
|
|
31405
|
+
*
|
|
31406
|
+
* async waitForInit() {
|
|
31407
|
+
* super.waitForInit(); // Call parent for logging
|
|
31408
|
+
* this.client = new BinanceClient(process.env.API_KEY, process.env.SECRET);
|
|
31409
|
+
* await this.client.connect();
|
|
31410
|
+
* }
|
|
31411
|
+
*
|
|
31412
|
+
* async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
|
|
31413
|
+
* super.onSignalOpenCommit(payload); // Call parent for logging
|
|
31414
|
+
* await this.client!.placeOrder({
|
|
31415
|
+
* symbol: payload.symbol,
|
|
31416
|
+
* side: payload.position === "long" ? "BUY" : "SELL",
|
|
31417
|
+
* quantity: payload.cost / payload.priceOpen,
|
|
31418
|
+
* });
|
|
31419
|
+
* }
|
|
31420
|
+
*
|
|
31421
|
+
* async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
|
|
31422
|
+
* super.onSignalCloseCommit(payload); // Call parent for logging
|
|
31423
|
+
* await this.client!.closePosition(payload.symbol);
|
|
31424
|
+
* }
|
|
31425
|
+
* }
|
|
31426
|
+
*
|
|
31427
|
+
* // Register the adapter
|
|
31428
|
+
* Broker.useBrokerAdapter(BinanceBroker);
|
|
31429
|
+
* Broker.enable();
|
|
31430
|
+
* ```
|
|
31431
|
+
*
|
|
31432
|
+
* @example
|
|
31433
|
+
* ```typescript
|
|
31434
|
+
* // Minimal implementation — only handle opens and closes
|
|
31435
|
+
* class NotifyBroker extends BrokerBase {
|
|
31436
|
+
* async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
|
|
31437
|
+
* await sendTelegram(`Opened ${payload.position} on ${payload.symbol}`);
|
|
31438
|
+
* }
|
|
31439
|
+
*
|
|
31440
|
+
* async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
|
|
31441
|
+
* const pnl = payload.pnl.profit - payload.pnl.loss;
|
|
31442
|
+
* await sendTelegram(`Closed ${payload.symbol}: PnL $${pnl.toFixed(2)}`);
|
|
31443
|
+
* }
|
|
31444
|
+
* }
|
|
31445
|
+
* ```
|
|
31446
|
+
*/
|
|
31447
|
+
class BrokerBase {
|
|
31448
|
+
/**
|
|
31449
|
+
* Performs async initialization before the broker starts receiving events.
|
|
31450
|
+
*
|
|
31451
|
+
* Called once by BrokerProxy via `waitForInit()` (singleshot) before the first event.
|
|
31452
|
+
* Override to establish exchange connections, authenticate API clients, load configuration.
|
|
31453
|
+
*
|
|
31454
|
+
* Default implementation: Logs initialization event.
|
|
31455
|
+
*
|
|
31456
|
+
* @example
|
|
31457
|
+
* ```typescript
|
|
31458
|
+
* async waitForInit() {
|
|
31459
|
+
* super.waitForInit(); // Keep parent logging
|
|
31460
|
+
* this.exchange = new ExchangeClient(process.env.API_KEY);
|
|
31461
|
+
* await this.exchange.authenticate();
|
|
31462
|
+
* }
|
|
31463
|
+
* ```
|
|
31464
|
+
*/
|
|
31465
|
+
async waitForInit() {
|
|
31466
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_WAIT_FOR_INIT, {});
|
|
31467
|
+
}
|
|
31468
|
+
/**
|
|
31469
|
+
* Called when a new position is opened (signal activated).
|
|
31470
|
+
*
|
|
31471
|
+
* Triggered automatically via syncSubject when a scheduled signal's priceOpen is hit.
|
|
31472
|
+
* Use to place the actual entry order on the exchange.
|
|
31473
|
+
*
|
|
31474
|
+
* Default implementation: Logs signal-open event.
|
|
31475
|
+
*
|
|
31476
|
+
* @param payload - Signal open details: symbol, cost, position, priceOpen, priceTakeProfit, priceStopLoss, context, backtest
|
|
31477
|
+
*
|
|
31478
|
+
* @example
|
|
31479
|
+
* ```typescript
|
|
31480
|
+
* async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
|
|
31481
|
+
* super.onSignalOpenCommit(payload); // Keep parent logging
|
|
31482
|
+
* await this.exchange.placeMarketOrder({
|
|
31483
|
+
* symbol: payload.symbol,
|
|
31484
|
+
* side: payload.position === "long" ? "BUY" : "SELL",
|
|
31485
|
+
* quantity: payload.cost / payload.priceOpen,
|
|
31486
|
+
* });
|
|
31487
|
+
* }
|
|
31488
|
+
* ```
|
|
31489
|
+
*/
|
|
31490
|
+
async onSignalOpenCommit(payload) {
|
|
31491
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_SIGNAL_OPEN, {
|
|
31492
|
+
symbol: payload.symbol,
|
|
31493
|
+
context: payload.context,
|
|
31494
|
+
});
|
|
31495
|
+
}
|
|
31496
|
+
/**
|
|
31497
|
+
* Called when a position is fully closed (SL/TP hit or manual close).
|
|
31498
|
+
*
|
|
31499
|
+
* Triggered automatically via syncSubject when a pending signal is closed.
|
|
31500
|
+
* Use to place the exit order and record final PnL.
|
|
31501
|
+
*
|
|
31502
|
+
* Default implementation: Logs signal-close event.
|
|
31503
|
+
*
|
|
31504
|
+
* @param payload - Signal close details: symbol, cost, position, currentPrice, pnl, totalEntries, totalPartials, context, backtest
|
|
31505
|
+
*
|
|
31506
|
+
* @example
|
|
31507
|
+
* ```typescript
|
|
31508
|
+
* async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
|
|
31509
|
+
* super.onSignalCloseCommit(payload); // Keep parent logging
|
|
31510
|
+
* await this.exchange.closePosition(payload.symbol);
|
|
31511
|
+
* await this.db.recordTrade({ symbol: payload.symbol, pnl: payload.pnl });
|
|
31512
|
+
* }
|
|
31513
|
+
* ```
|
|
31514
|
+
*/
|
|
31515
|
+
async onSignalCloseCommit(payload) {
|
|
31516
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_SIGNAL_CLOSE, {
|
|
31517
|
+
symbol: payload.symbol,
|
|
31518
|
+
context: payload.context,
|
|
31519
|
+
});
|
|
31520
|
+
}
|
|
31521
|
+
/**
|
|
31522
|
+
* Called when a partial close at profit is executed.
|
|
31523
|
+
*
|
|
31524
|
+
* Triggered explicitly from strategy.ts / Live.ts / Backtest.ts after all validations pass,
|
|
31525
|
+
* before `strategyCoreService.partialProfit()`. If this method throws, the DI mutation is skipped.
|
|
31526
|
+
* Use to partially close the position on the exchange at the profit level.
|
|
31527
|
+
*
|
|
31528
|
+
* Default implementation: Logs partial profit event.
|
|
31529
|
+
*
|
|
31530
|
+
* @param payload - Partial profit details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest
|
|
31531
|
+
*
|
|
31532
|
+
* @example
|
|
31533
|
+
* ```typescript
|
|
31534
|
+
* async onPartialProfitCommit(payload: BrokerPartialProfitPayload) {
|
|
31535
|
+
* super.onPartialProfitCommit(payload); // Keep parent logging
|
|
31536
|
+
* await this.exchange.reducePosition({
|
|
31537
|
+
* symbol: payload.symbol,
|
|
31538
|
+
* dollarAmount: payload.cost,
|
|
31539
|
+
* price: payload.currentPrice,
|
|
31540
|
+
* });
|
|
31541
|
+
* }
|
|
31542
|
+
* ```
|
|
31543
|
+
*/
|
|
31544
|
+
async onPartialProfitCommit(payload) {
|
|
31545
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_PARTIAL_PROFIT, {
|
|
31546
|
+
symbol: payload.symbol,
|
|
31547
|
+
context: payload.context,
|
|
31548
|
+
});
|
|
31549
|
+
}
|
|
31550
|
+
/**
|
|
31551
|
+
* Called when a partial close at loss is executed.
|
|
31552
|
+
*
|
|
31553
|
+
* Triggered explicitly from strategy.ts / Live.ts / Backtest.ts after all validations pass,
|
|
31554
|
+
* before `strategyCoreService.partialLoss()`. If this method throws, the DI mutation is skipped.
|
|
31555
|
+
* Use to partially close the position on the exchange at the loss level.
|
|
31556
|
+
*
|
|
31557
|
+
* Default implementation: Logs partial loss event.
|
|
31558
|
+
*
|
|
31559
|
+
* @param payload - Partial loss details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest
|
|
31560
|
+
*
|
|
31561
|
+
* @example
|
|
31562
|
+
* ```typescript
|
|
31563
|
+
* async onPartialLossCommit(payload: BrokerPartialLossPayload) {
|
|
31564
|
+
* super.onPartialLossCommit(payload); // Keep parent logging
|
|
31565
|
+
* await this.exchange.reducePosition({
|
|
31566
|
+
* symbol: payload.symbol,
|
|
31567
|
+
* dollarAmount: payload.cost,
|
|
31568
|
+
* price: payload.currentPrice,
|
|
31569
|
+
* });
|
|
31570
|
+
* }
|
|
31571
|
+
* ```
|
|
31572
|
+
*/
|
|
31573
|
+
async onPartialLossCommit(payload) {
|
|
31574
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_PARTIAL_LOSS, {
|
|
31575
|
+
symbol: payload.symbol,
|
|
31576
|
+
context: payload.context,
|
|
31577
|
+
});
|
|
31578
|
+
}
|
|
31579
|
+
/**
|
|
31580
|
+
* Called when the trailing stop-loss level is updated.
|
|
31581
|
+
*
|
|
31582
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.trailingStop()`.
|
|
31583
|
+
* `newStopLossPrice` is the absolute SL price — use it to update the exchange order directly.
|
|
31584
|
+
*
|
|
31585
|
+
* Default implementation: Logs trailing stop event.
|
|
31586
|
+
*
|
|
31587
|
+
* @param payload - Trailing stop details: symbol, percentShift, currentPrice, newStopLossPrice, context, backtest
|
|
31588
|
+
*
|
|
31589
|
+
* @example
|
|
31590
|
+
* ```typescript
|
|
31591
|
+
* async onTrailingStopCommit(payload: BrokerTrailingStopPayload) {
|
|
31592
|
+
* super.onTrailingStopCommit(payload); // Keep parent logging
|
|
31593
|
+
* await this.exchange.updateStopLoss({
|
|
31594
|
+
* symbol: payload.symbol,
|
|
31595
|
+
* price: payload.newStopLossPrice,
|
|
31596
|
+
* });
|
|
31597
|
+
* }
|
|
31598
|
+
* ```
|
|
31599
|
+
*/
|
|
31600
|
+
async onTrailingStopCommit(payload) {
|
|
31601
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_TRAILING_STOP, {
|
|
31602
|
+
symbol: payload.symbol,
|
|
31603
|
+
context: payload.context,
|
|
31604
|
+
});
|
|
31605
|
+
}
|
|
31606
|
+
/**
|
|
31607
|
+
* Called when the trailing take-profit level is updated.
|
|
31608
|
+
*
|
|
31609
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.trailingTake()`.
|
|
31610
|
+
* `newTakeProfitPrice` is the absolute TP price — use it to update the exchange order directly.
|
|
31611
|
+
*
|
|
31612
|
+
* Default implementation: Logs trailing take event.
|
|
31613
|
+
*
|
|
31614
|
+
* @param payload - Trailing take details: symbol, percentShift, currentPrice, newTakeProfitPrice, context, backtest
|
|
31615
|
+
*
|
|
31616
|
+
* @example
|
|
31617
|
+
* ```typescript
|
|
31618
|
+
* async onTrailingTakeCommit(payload: BrokerTrailingTakePayload) {
|
|
31619
|
+
* super.onTrailingTakeCommit(payload); // Keep parent logging
|
|
31620
|
+
* await this.exchange.updateTakeProfit({
|
|
31621
|
+
* symbol: payload.symbol,
|
|
31622
|
+
* price: payload.newTakeProfitPrice,
|
|
31623
|
+
* });
|
|
31624
|
+
* }
|
|
31625
|
+
* ```
|
|
31626
|
+
*/
|
|
31627
|
+
async onTrailingTakeCommit(payload) {
|
|
31628
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_TRAILING_TAKE, {
|
|
31629
|
+
symbol: payload.symbol,
|
|
31630
|
+
context: payload.context,
|
|
31631
|
+
});
|
|
31632
|
+
}
|
|
31633
|
+
/**
|
|
31634
|
+
* Called when the stop-loss is moved to breakeven (entry price).
|
|
31635
|
+
*
|
|
31636
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.breakeven()`.
|
|
31637
|
+
* `newStopLossPrice` equals `effectivePriceOpen` — the position's effective entry price.
|
|
31638
|
+
* `newTakeProfitPrice` is unchanged by breakeven.
|
|
31639
|
+
*
|
|
31640
|
+
* Default implementation: Logs breakeven event.
|
|
31641
|
+
*
|
|
31642
|
+
* @param payload - Breakeven details: symbol, currentPrice, newStopLossPrice, newTakeProfitPrice, context, backtest
|
|
31643
|
+
*
|
|
31644
|
+
* @example
|
|
31645
|
+
* ```typescript
|
|
31646
|
+
* async onBreakevenCommit(payload: BrokerBreakevenPayload) {
|
|
31647
|
+
* super.onBreakevenCommit(payload); // Keep parent logging
|
|
31648
|
+
* await this.exchange.updateStopLoss({
|
|
31649
|
+
* symbol: payload.symbol,
|
|
31650
|
+
* price: payload.newStopLossPrice, // = entry price
|
|
31651
|
+
* });
|
|
31652
|
+
* }
|
|
31653
|
+
* ```
|
|
31654
|
+
*/
|
|
31655
|
+
async onBreakevenCommit(payload) {
|
|
31656
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_BREAKEVEN, {
|
|
31657
|
+
symbol: payload.symbol,
|
|
31658
|
+
context: payload.context,
|
|
31659
|
+
});
|
|
31660
|
+
}
|
|
31661
|
+
/**
|
|
31662
|
+
* Called when a new DCA entry is added to the active position.
|
|
31663
|
+
*
|
|
31664
|
+
* Triggered explicitly after all validations pass, before `strategyCoreService.averageBuy()`.
|
|
31665
|
+
* `currentPrice` is the market price at which the new averaging entry is placed.
|
|
31666
|
+
* `cost` is the dollar amount of the new DCA entry.
|
|
31667
|
+
*
|
|
31668
|
+
* Default implementation: Logs average buy event.
|
|
31669
|
+
*
|
|
31670
|
+
* @param payload - Average buy details: symbol, currentPrice, cost, context, backtest
|
|
31671
|
+
*
|
|
31672
|
+
* @example
|
|
31673
|
+
* ```typescript
|
|
31674
|
+
* async onAverageBuyCommit(payload: BrokerAverageBuyPayload) {
|
|
31675
|
+
* super.onAverageBuyCommit(payload); // Keep parent logging
|
|
31676
|
+
* await this.exchange.placeMarketOrder({
|
|
31677
|
+
* symbol: payload.symbol,
|
|
31678
|
+
* side: "BUY",
|
|
31679
|
+
* quantity: payload.cost / payload.currentPrice,
|
|
31680
|
+
* });
|
|
31681
|
+
* }
|
|
31682
|
+
* ```
|
|
31683
|
+
*/
|
|
31684
|
+
async onAverageBuyCommit(payload) {
|
|
31685
|
+
bt.loggerService.info(BROKER_BASE_METHOD_NAME_ON_AVERAGE_BUY, {
|
|
31686
|
+
symbol: payload.symbol,
|
|
31687
|
+
context: payload.context,
|
|
31688
|
+
});
|
|
31689
|
+
}
|
|
31690
|
+
}
|
|
31691
|
+
// @ts-ignore
|
|
31692
|
+
BrokerBase = makeExtendable(BrokerBase);
|
|
31693
|
+
/**
|
|
31694
|
+
* Global singleton instance of BrokerAdapter.
|
|
31695
|
+
* Provides static-like access to all broker commit methods and lifecycle controls.
|
|
31696
|
+
*
|
|
31697
|
+
* @example
|
|
31698
|
+
* ```typescript
|
|
31699
|
+
* import { Broker } from "backtest-kit";
|
|
31700
|
+
*
|
|
31701
|
+
* Broker.useBrokerAdapter(MyBrokerAdapter);
|
|
31702
|
+
* const dispose = Broker.enable();
|
|
31703
|
+
* ```
|
|
31704
|
+
*/
|
|
31705
|
+
const Broker = new BrokerAdapter();
|
|
31706
|
+
|
|
30140
31707
|
const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
|
|
30141
31708
|
const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
|
|
30142
31709
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
@@ -30145,6 +31712,8 @@ const PARTIAL_PROFIT_COST_METHOD_NAME = "strategy.commitPartialProfitCost";
|
|
|
30145
31712
|
const PARTIAL_LOSS_COST_METHOD_NAME = "strategy.commitPartialLossCost";
|
|
30146
31713
|
const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
|
|
30147
31714
|
const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
|
|
31715
|
+
const TRAILING_STOP_COST_METHOD_NAME = "strategy.commitTrailingStopCost";
|
|
31716
|
+
const TRAILING_PROFIT_COST_METHOD_NAME = "strategy.commitTrailingTakeCost";
|
|
30148
31717
|
const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
|
|
30149
31718
|
const ACTIVATE_SCHEDULED_METHOD_NAME = "strategy.commitActivateScheduled";
|
|
30150
31719
|
const AVERAGE_BUY_METHOD_NAME = "strategy.commitAverageBuy";
|
|
@@ -30274,6 +31843,28 @@ async function commitPartialProfit(symbol, percentToClose) {
|
|
|
30274
31843
|
const currentPrice = await getAveragePrice(symbol);
|
|
30275
31844
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30276
31845
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
31846
|
+
const investedCostForProfit = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
31847
|
+
if (investedCostForProfit === null) {
|
|
31848
|
+
return false;
|
|
31849
|
+
}
|
|
31850
|
+
const signalForProfit = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
31851
|
+
if (!signalForProfit) {
|
|
31852
|
+
return false;
|
|
31853
|
+
}
|
|
31854
|
+
if (await not(bt.strategyCoreService.validatePartialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
31855
|
+
return false;
|
|
31856
|
+
}
|
|
31857
|
+
await Broker.commitPartialProfit({
|
|
31858
|
+
symbol,
|
|
31859
|
+
percentToClose,
|
|
31860
|
+
cost: percentToCloseCost(percentToClose, investedCostForProfit),
|
|
31861
|
+
currentPrice,
|
|
31862
|
+
position: signalForProfit.position,
|
|
31863
|
+
priceTakeProfit: signalForProfit.priceTakeProfit,
|
|
31864
|
+
priceStopLoss: signalForProfit.priceStopLoss,
|
|
31865
|
+
context: { exchangeName, frameName, strategyName },
|
|
31866
|
+
backtest: isBacktest,
|
|
31867
|
+
});
|
|
30277
31868
|
return await bt.strategyCoreService.partialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30278
31869
|
}
|
|
30279
31870
|
/**
|
|
@@ -30317,6 +31908,28 @@ async function commitPartialLoss(symbol, percentToClose) {
|
|
|
30317
31908
|
const currentPrice = await getAveragePrice(symbol);
|
|
30318
31909
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30319
31910
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
31911
|
+
const investedCostForLoss = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
31912
|
+
if (investedCostForLoss === null) {
|
|
31913
|
+
return false;
|
|
31914
|
+
}
|
|
31915
|
+
const signalForLoss = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
31916
|
+
if (!signalForLoss) {
|
|
31917
|
+
return false;
|
|
31918
|
+
}
|
|
31919
|
+
if (await not(bt.strategyCoreService.validatePartialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
31920
|
+
return false;
|
|
31921
|
+
}
|
|
31922
|
+
await Broker.commitPartialLoss({
|
|
31923
|
+
symbol,
|
|
31924
|
+
percentToClose,
|
|
31925
|
+
cost: percentToCloseCost(percentToClose, investedCostForLoss),
|
|
31926
|
+
currentPrice,
|
|
31927
|
+
position: signalForLoss.position,
|
|
31928
|
+
priceTakeProfit: signalForLoss.priceTakeProfit,
|
|
31929
|
+
priceStopLoss: signalForLoss.priceStopLoss,
|
|
31930
|
+
context: { exchangeName, frameName, strategyName },
|
|
31931
|
+
backtest: isBacktest,
|
|
31932
|
+
});
|
|
30320
31933
|
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30321
31934
|
}
|
|
30322
31935
|
/**
|
|
@@ -30376,6 +31989,26 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
30376
31989
|
}
|
|
30377
31990
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30378
31991
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
31992
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
31993
|
+
if (!signal) {
|
|
31994
|
+
return false;
|
|
31995
|
+
}
|
|
31996
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
31997
|
+
if (effectivePriceOpen === null) {
|
|
31998
|
+
return false;
|
|
31999
|
+
}
|
|
32000
|
+
if (await not(bt.strategyCoreService.validateTrailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32001
|
+
return false;
|
|
32002
|
+
}
|
|
32003
|
+
await Broker.commitTrailingStop({
|
|
32004
|
+
symbol,
|
|
32005
|
+
percentShift,
|
|
32006
|
+
currentPrice,
|
|
32007
|
+
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
32008
|
+
position: signal.position,
|
|
32009
|
+
context: { exchangeName, frameName, strategyName },
|
|
32010
|
+
backtest: isBacktest,
|
|
32011
|
+
});
|
|
30379
32012
|
return await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
30380
32013
|
}
|
|
30381
32014
|
/**
|
|
@@ -30435,6 +32068,126 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
30435
32068
|
}
|
|
30436
32069
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30437
32070
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32071
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32072
|
+
if (!signal) {
|
|
32073
|
+
return false;
|
|
32074
|
+
}
|
|
32075
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32076
|
+
if (effectivePriceOpen === null) {
|
|
32077
|
+
return false;
|
|
32078
|
+
}
|
|
32079
|
+
if (await not(bt.strategyCoreService.validateTrailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32080
|
+
return false;
|
|
32081
|
+
}
|
|
32082
|
+
await Broker.commitTrailingTake({
|
|
32083
|
+
symbol,
|
|
32084
|
+
percentShift,
|
|
32085
|
+
currentPrice,
|
|
32086
|
+
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
32087
|
+
position: signal.position,
|
|
32088
|
+
context: { exchangeName, frameName, strategyName },
|
|
32089
|
+
backtest: isBacktest,
|
|
32090
|
+
});
|
|
32091
|
+
return await bt.strategyCoreService.trailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
32092
|
+
}
|
|
32093
|
+
/**
|
|
32094
|
+
* Adjusts the trailing stop-loss to an absolute price level.
|
|
32095
|
+
*
|
|
32096
|
+
* Convenience wrapper around commitTrailingStop that converts an absolute
|
|
32097
|
+
* stop-loss price to a percentShift relative to the ORIGINAL SL distance.
|
|
32098
|
+
*
|
|
32099
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32100
|
+
* Automatically fetches current price via getAveragePrice.
|
|
32101
|
+
*
|
|
32102
|
+
* @param symbol - Trading pair symbol
|
|
32103
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
32104
|
+
* @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
|
|
32105
|
+
*/
|
|
32106
|
+
async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
32107
|
+
bt.loggerService.info(TRAILING_STOP_COST_METHOD_NAME, {
|
|
32108
|
+
symbol,
|
|
32109
|
+
newStopLossPrice,
|
|
32110
|
+
});
|
|
32111
|
+
if (!ExecutionContextService.hasContext()) {
|
|
32112
|
+
throw new Error("commitTrailingStopCost requires an execution context");
|
|
32113
|
+
}
|
|
32114
|
+
if (!MethodContextService.hasContext()) {
|
|
32115
|
+
throw new Error("commitTrailingStopCost requires a method context");
|
|
32116
|
+
}
|
|
32117
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
32118
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
32119
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32120
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32121
|
+
if (!signal) {
|
|
32122
|
+
return false;
|
|
32123
|
+
}
|
|
32124
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32125
|
+
if (effectivePriceOpen === null) {
|
|
32126
|
+
return false;
|
|
32127
|
+
}
|
|
32128
|
+
const percentShift = slPriceToPercentShift(newStopLossPrice, signal.priceStopLoss, effectivePriceOpen);
|
|
32129
|
+
if (await not(bt.strategyCoreService.validateTrailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32130
|
+
return false;
|
|
32131
|
+
}
|
|
32132
|
+
await Broker.commitTrailingStop({
|
|
32133
|
+
symbol,
|
|
32134
|
+
percentShift,
|
|
32135
|
+
currentPrice,
|
|
32136
|
+
newStopLossPrice,
|
|
32137
|
+
position: signal.position,
|
|
32138
|
+
context: { exchangeName, frameName, strategyName },
|
|
32139
|
+
backtest: isBacktest,
|
|
32140
|
+
});
|
|
32141
|
+
return await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
32142
|
+
}
|
|
32143
|
+
/**
|
|
32144
|
+
* Adjusts the trailing take-profit to an absolute price level.
|
|
32145
|
+
*
|
|
32146
|
+
* Convenience wrapper around commitTrailingTake that converts an absolute
|
|
32147
|
+
* take-profit price to a percentShift relative to the ORIGINAL TP distance.
|
|
32148
|
+
*
|
|
32149
|
+
* Automatically detects backtest/live mode from execution context.
|
|
32150
|
+
* Automatically fetches current price via getAveragePrice.
|
|
32151
|
+
*
|
|
32152
|
+
* @param symbol - Trading pair symbol
|
|
32153
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
32154
|
+
* @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
|
|
32155
|
+
*/
|
|
32156
|
+
async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
32157
|
+
bt.loggerService.info(TRAILING_PROFIT_COST_METHOD_NAME, {
|
|
32158
|
+
symbol,
|
|
32159
|
+
newTakeProfitPrice,
|
|
32160
|
+
});
|
|
32161
|
+
if (!ExecutionContextService.hasContext()) {
|
|
32162
|
+
throw new Error("commitTrailingTakeCost requires an execution context");
|
|
32163
|
+
}
|
|
32164
|
+
if (!MethodContextService.hasContext()) {
|
|
32165
|
+
throw new Error("commitTrailingTakeCost requires a method context");
|
|
32166
|
+
}
|
|
32167
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
32168
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
32169
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32170
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32171
|
+
if (!signal) {
|
|
32172
|
+
return false;
|
|
32173
|
+
}
|
|
32174
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32175
|
+
if (effectivePriceOpen === null) {
|
|
32176
|
+
return false;
|
|
32177
|
+
}
|
|
32178
|
+
const percentShift = tpPriceToPercentShift(newTakeProfitPrice, signal.priceTakeProfit, effectivePriceOpen);
|
|
32179
|
+
if (await not(bt.strategyCoreService.validateTrailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32180
|
+
return false;
|
|
32181
|
+
}
|
|
32182
|
+
await Broker.commitTrailingTake({
|
|
32183
|
+
symbol,
|
|
32184
|
+
percentShift,
|
|
32185
|
+
currentPrice,
|
|
32186
|
+
newTakeProfitPrice,
|
|
32187
|
+
position: signal.position,
|
|
32188
|
+
context: { exchangeName, frameName, strategyName },
|
|
32189
|
+
backtest: isBacktest,
|
|
32190
|
+
});
|
|
30438
32191
|
return await bt.strategyCoreService.trailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
30439
32192
|
}
|
|
30440
32193
|
/**
|
|
@@ -30475,6 +32228,26 @@ async function commitBreakeven(symbol) {
|
|
|
30475
32228
|
const currentPrice = await getAveragePrice(symbol);
|
|
30476
32229
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30477
32230
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32231
|
+
const signal = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32232
|
+
if (!signal) {
|
|
32233
|
+
return false;
|
|
32234
|
+
}
|
|
32235
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32236
|
+
if (effectivePriceOpen === null) {
|
|
32237
|
+
return false;
|
|
32238
|
+
}
|
|
32239
|
+
if (await not(bt.strategyCoreService.validateBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32240
|
+
return false;
|
|
32241
|
+
}
|
|
32242
|
+
await Broker.commitBreakeven({
|
|
32243
|
+
symbol,
|
|
32244
|
+
currentPrice,
|
|
32245
|
+
newStopLossPrice: breakevenNewStopLossPrice(effectivePriceOpen),
|
|
32246
|
+
newTakeProfitPrice: breakevenNewTakeProfitPrice(signal.priceTakeProfit, signal._trailingPriceTakeProfit),
|
|
32247
|
+
position: signal.position,
|
|
32248
|
+
context: { exchangeName, frameName, strategyName },
|
|
32249
|
+
backtest: isBacktest,
|
|
32250
|
+
});
|
|
30478
32251
|
return await bt.strategyCoreService.breakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
30479
32252
|
}
|
|
30480
32253
|
/**
|
|
@@ -30549,6 +32322,23 @@ async function commitAverageBuy(symbol, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_C
|
|
|
30549
32322
|
const currentPrice = await getAveragePrice(symbol);
|
|
30550
32323
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30551
32324
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32325
|
+
if (await not(bt.strategyCoreService.validateAverageBuy(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32326
|
+
return false;
|
|
32327
|
+
}
|
|
32328
|
+
const signalForAvgBuy = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32329
|
+
if (!signalForAvgBuy) {
|
|
32330
|
+
return false;
|
|
32331
|
+
}
|
|
32332
|
+
await Broker.commitAverageBuy({
|
|
32333
|
+
symbol,
|
|
32334
|
+
currentPrice,
|
|
32335
|
+
cost,
|
|
32336
|
+
position: signalForAvgBuy.position,
|
|
32337
|
+
priceTakeProfit: signalForAvgBuy.priceTakeProfit,
|
|
32338
|
+
priceStopLoss: signalForAvgBuy.priceStopLoss,
|
|
32339
|
+
context: { exchangeName, frameName, strategyName },
|
|
32340
|
+
backtest: isBacktest,
|
|
32341
|
+
});
|
|
30552
32342
|
return await bt.strategyCoreService.averageBuy(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName }, cost);
|
|
30553
32343
|
}
|
|
30554
32344
|
/**
|
|
@@ -30721,7 +32511,9 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
30721
32511
|
return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
30722
32512
|
}
|
|
30723
32513
|
async function getPositionAveragePrice(symbol) {
|
|
30724
|
-
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
32514
|
+
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
32515
|
+
symbol,
|
|
32516
|
+
});
|
|
30725
32517
|
if (!ExecutionContextService.hasContext()) {
|
|
30726
32518
|
throw new Error("getPositionAveragePrice requires an execution context");
|
|
30727
32519
|
}
|
|
@@ -30733,7 +32525,9 @@ async function getPositionAveragePrice(symbol) {
|
|
|
30733
32525
|
return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30734
32526
|
}
|
|
30735
32527
|
async function getPositionInvestedCount(symbol) {
|
|
30736
|
-
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
|
|
32528
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
|
|
32529
|
+
symbol,
|
|
32530
|
+
});
|
|
30737
32531
|
if (!ExecutionContextService.hasContext()) {
|
|
30738
32532
|
throw new Error("getPositionInvestedCount requires an execution context");
|
|
30739
32533
|
}
|
|
@@ -30745,7 +32539,9 @@ async function getPositionInvestedCount(symbol) {
|
|
|
30745
32539
|
return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30746
32540
|
}
|
|
30747
32541
|
async function getPositionInvestedCost(symbol) {
|
|
30748
|
-
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
|
|
32542
|
+
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
|
|
32543
|
+
symbol,
|
|
32544
|
+
});
|
|
30749
32545
|
if (!ExecutionContextService.hasContext()) {
|
|
30750
32546
|
throw new Error("getPositionInvestedCost requires an execution context");
|
|
30751
32547
|
}
|
|
@@ -30799,7 +32595,10 @@ async function getPositionPnlPercent(symbol) {
|
|
|
30799
32595
|
* ```
|
|
30800
32596
|
*/
|
|
30801
32597
|
async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
30802
|
-
bt.loggerService.info(PARTIAL_PROFIT_COST_METHOD_NAME, {
|
|
32598
|
+
bt.loggerService.info(PARTIAL_PROFIT_COST_METHOD_NAME, {
|
|
32599
|
+
symbol,
|
|
32600
|
+
dollarAmount,
|
|
32601
|
+
});
|
|
30803
32602
|
if (!ExecutionContextService.hasContext()) {
|
|
30804
32603
|
throw new Error("commitPartialProfitCost requires an execution context");
|
|
30805
32604
|
}
|
|
@@ -30810,9 +32609,28 @@ async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
|
30810
32609
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30811
32610
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
30812
32611
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30813
|
-
if (investedCost === null)
|
|
32612
|
+
if (investedCost === null) {
|
|
32613
|
+
return false;
|
|
32614
|
+
}
|
|
32615
|
+
const signalForProfitCost = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32616
|
+
if (!signalForProfitCost) {
|
|
30814
32617
|
return false;
|
|
32618
|
+
}
|
|
30815
32619
|
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
32620
|
+
if (await not(bt.strategyCoreService.validatePartialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32621
|
+
return false;
|
|
32622
|
+
}
|
|
32623
|
+
await Broker.commitPartialProfit({
|
|
32624
|
+
symbol,
|
|
32625
|
+
percentToClose,
|
|
32626
|
+
cost: dollarAmount,
|
|
32627
|
+
currentPrice,
|
|
32628
|
+
position: signalForProfitCost.position,
|
|
32629
|
+
priceTakeProfit: signalForProfitCost.priceTakeProfit,
|
|
32630
|
+
priceStopLoss: signalForProfitCost.priceStopLoss,
|
|
32631
|
+
context: { exchangeName, frameName, strategyName },
|
|
32632
|
+
backtest: isBacktest,
|
|
32633
|
+
});
|
|
30816
32634
|
return await bt.strategyCoreService.partialProfit(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30817
32635
|
}
|
|
30818
32636
|
/**
|
|
@@ -30845,7 +32663,10 @@ async function commitPartialProfitCost(symbol, dollarAmount) {
|
|
|
30845
32663
|
* ```
|
|
30846
32664
|
*/
|
|
30847
32665
|
async function commitPartialLossCost(symbol, dollarAmount) {
|
|
30848
|
-
bt.loggerService.info(PARTIAL_LOSS_COST_METHOD_NAME, {
|
|
32666
|
+
bt.loggerService.info(PARTIAL_LOSS_COST_METHOD_NAME, {
|
|
32667
|
+
symbol,
|
|
32668
|
+
dollarAmount,
|
|
32669
|
+
});
|
|
30849
32670
|
if (!ExecutionContextService.hasContext()) {
|
|
30850
32671
|
throw new Error("commitPartialLossCost requires an execution context");
|
|
30851
32672
|
}
|
|
@@ -30856,9 +32677,28 @@ async function commitPartialLossCost(symbol, dollarAmount) {
|
|
|
30856
32677
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
30857
32678
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
30858
32679
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30859
|
-
if (investedCost === null)
|
|
32680
|
+
if (investedCost === null) {
|
|
30860
32681
|
return false;
|
|
32682
|
+
}
|
|
32683
|
+
const signalForLossCost = await bt.strategyCoreService.getPendingSignal(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32684
|
+
if (!signalForLossCost) {
|
|
32685
|
+
return false;
|
|
32686
|
+
}
|
|
30861
32687
|
const percentToClose = investedCostToPercent(dollarAmount, investedCost);
|
|
32688
|
+
if (await not(bt.strategyCoreService.validatePartialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName }))) {
|
|
32689
|
+
return false;
|
|
32690
|
+
}
|
|
32691
|
+
await Broker.commitPartialLoss({
|
|
32692
|
+
symbol,
|
|
32693
|
+
percentToClose,
|
|
32694
|
+
cost: dollarAmount,
|
|
32695
|
+
currentPrice,
|
|
32696
|
+
position: signalForLossCost.position,
|
|
32697
|
+
priceTakeProfit: signalForLossCost.priceTakeProfit,
|
|
32698
|
+
priceStopLoss: signalForLossCost.priceStopLoss,
|
|
32699
|
+
context: { exchangeName, frameName, strategyName },
|
|
32700
|
+
backtest: isBacktest,
|
|
32701
|
+
});
|
|
30862
32702
|
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
30863
32703
|
}
|
|
30864
32704
|
async function getPositionPnlCost(symbol) {
|
|
@@ -30907,6 +32747,34 @@ async function getPositionLevels(symbol) {
|
|
|
30907
32747
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
30908
32748
|
return await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
30909
32749
|
}
|
|
32750
|
+
/**
|
|
32751
|
+
* Returns the list of partial close events for the current pending signal.
|
|
32752
|
+
*
|
|
32753
|
+
* Each element represents a partial profit or loss close executed via
|
|
32754
|
+
* commitPartialProfit / commitPartialLoss (or their Cost variants).
|
|
32755
|
+
*
|
|
32756
|
+
* Returns null if no pending signal exists.
|
|
32757
|
+
* Returns an empty array if no partials were executed yet.
|
|
32758
|
+
*
|
|
32759
|
+
* Each entry contains:
|
|
32760
|
+
* - `type` — "profit" or "loss"
|
|
32761
|
+
* - `percent` — percentage of position closed at this partial
|
|
32762
|
+
* - `currentPrice` — execution price of the partial close
|
|
32763
|
+
* - `costBasisAtClose` — accounting cost basis at the moment of this partial
|
|
32764
|
+
* - `entryCountAtClose` — number of DCA entries accumulated at this partial
|
|
32765
|
+
*
|
|
32766
|
+
* @param symbol - Trading pair symbol
|
|
32767
|
+
* @returns Promise resolving to array of partial close records or null
|
|
32768
|
+
*
|
|
32769
|
+
* @example
|
|
32770
|
+
* ```typescript
|
|
32771
|
+
* import { getPositionPartials } from "backtest-kit";
|
|
32772
|
+
*
|
|
32773
|
+
* const partials = await getPositionPartials("BTCUSDT");
|
|
32774
|
+
* // No partials yet: []
|
|
32775
|
+
* // After one partial profit: [{ type: "profit", percent: 50, currentPrice: 45000, ... }]
|
|
32776
|
+
* ```
|
|
32777
|
+
*/
|
|
30910
32778
|
async function getPositionPartials(symbol) {
|
|
30911
32779
|
bt.loggerService.info(GET_POSITION_PARTIALS_METHOD_NAME, { symbol });
|
|
30912
32780
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32153,6 +34021,8 @@ const BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST = "BacktestUtils.commitPartialPro
|
|
|
32153
34021
|
const BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST = "BacktestUtils.commitPartialLossCost";
|
|
32154
34022
|
const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
|
|
32155
34023
|
const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
|
|
34024
|
+
const BACKTEST_METHOD_NAME_TRAILING_STOP_COST = "BacktestUtils.commitTrailingStopCost";
|
|
34025
|
+
const BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST = "BacktestUtils.commitTrailingTakeCost";
|
|
32156
34026
|
const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
|
|
32157
34027
|
const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
|
|
32158
34028
|
const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
|
|
@@ -32332,12 +34202,13 @@ class BacktestInstance {
|
|
|
32332
34202
|
}
|
|
32333
34203
|
{
|
|
32334
34204
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
32335
|
-
riskName &&
|
|
32336
|
-
|
|
32337
|
-
|
|
32338
|
-
|
|
32339
|
-
|
|
32340
|
-
|
|
34205
|
+
riskName &&
|
|
34206
|
+
bt.riskGlobalService.clear({
|
|
34207
|
+
riskName,
|
|
34208
|
+
exchangeName: context.exchangeName,
|
|
34209
|
+
frameName: context.frameName,
|
|
34210
|
+
backtest: true,
|
|
34211
|
+
});
|
|
32341
34212
|
riskList &&
|
|
32342
34213
|
riskList.forEach((riskName) => bt.riskGlobalService.clear({
|
|
32343
34214
|
riskName,
|
|
@@ -32687,6 +34558,16 @@ class BacktestUtils {
|
|
|
32687
34558
|
}
|
|
32688
34559
|
return await bt.strategyCoreService.getBreakeven(true, symbol, currentPrice, context);
|
|
32689
34560
|
};
|
|
34561
|
+
/**
|
|
34562
|
+
* Returns the effective (weighted average) entry price for the current pending signal.
|
|
34563
|
+
*
|
|
34564
|
+
* Accounts for all DCA entries via commitAverageBuy.
|
|
34565
|
+
* Returns null if no pending signal exists.
|
|
34566
|
+
*
|
|
34567
|
+
* @param symbol - Trading pair symbol
|
|
34568
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34569
|
+
* @returns Effective entry price, or null if no active position
|
|
34570
|
+
*/
|
|
32690
34571
|
this.getPositionAveragePrice = async (symbol, context) => {
|
|
32691
34572
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
32692
34573
|
symbol,
|
|
@@ -32705,6 +34586,15 @@ class BacktestUtils {
|
|
|
32705
34586
|
}
|
|
32706
34587
|
return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
32707
34588
|
};
|
|
34589
|
+
/**
|
|
34590
|
+
* Returns the total number of base-asset units currently held in the position.
|
|
34591
|
+
*
|
|
34592
|
+
* Includes units from all DCA entries. Returns null if no pending signal exists.
|
|
34593
|
+
*
|
|
34594
|
+
* @param symbol - Trading pair symbol
|
|
34595
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34596
|
+
* @returns Total units held, or null if no active position
|
|
34597
|
+
*/
|
|
32708
34598
|
this.getPositionInvestedCount = async (symbol, context) => {
|
|
32709
34599
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
32710
34600
|
symbol,
|
|
@@ -32723,6 +34613,15 @@ class BacktestUtils {
|
|
|
32723
34613
|
}
|
|
32724
34614
|
return await bt.strategyCoreService.getPositionInvestedCount(true, symbol, context);
|
|
32725
34615
|
};
|
|
34616
|
+
/**
|
|
34617
|
+
* Returns the total dollar cost invested in the current position.
|
|
34618
|
+
*
|
|
34619
|
+
* Sum of all entry costs across DCA entries. Returns null if no pending signal exists.
|
|
34620
|
+
*
|
|
34621
|
+
* @param symbol - Trading pair symbol
|
|
34622
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34623
|
+
* @returns Total invested cost in quote currency, or null if no active position
|
|
34624
|
+
*/
|
|
32726
34625
|
this.getPositionInvestedCost = async (symbol, context) => {
|
|
32727
34626
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
32728
34627
|
symbol,
|
|
@@ -32741,6 +34640,17 @@ class BacktestUtils {
|
|
|
32741
34640
|
}
|
|
32742
34641
|
return await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
32743
34642
|
};
|
|
34643
|
+
/**
|
|
34644
|
+
* Returns the current unrealized PnL as a percentage of the invested cost.
|
|
34645
|
+
*
|
|
34646
|
+
* Calculated relative to the effective (weighted average) entry price.
|
|
34647
|
+
* Positive for profit, negative for loss. Returns null if no pending signal exists.
|
|
34648
|
+
*
|
|
34649
|
+
* @param symbol - Trading pair symbol
|
|
34650
|
+
* @param currentPrice - Current market price
|
|
34651
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34652
|
+
* @returns PnL percentage, or null if no active position
|
|
34653
|
+
*/
|
|
32744
34654
|
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
32745
34655
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
32746
34656
|
symbol,
|
|
@@ -32760,6 +34670,17 @@ class BacktestUtils {
|
|
|
32760
34670
|
}
|
|
32761
34671
|
return await bt.strategyCoreService.getPositionPnlPercent(true, symbol, currentPrice, context);
|
|
32762
34672
|
};
|
|
34673
|
+
/**
|
|
34674
|
+
* Returns the current unrealized PnL in quote currency (dollar amount).
|
|
34675
|
+
*
|
|
34676
|
+
* Calculated as (currentPrice - effectiveEntry) * units for LONG,
|
|
34677
|
+
* reversed for SHORT. Returns null if no pending signal exists.
|
|
34678
|
+
*
|
|
34679
|
+
* @param symbol - Trading pair symbol
|
|
34680
|
+
* @param currentPrice - Current market price
|
|
34681
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34682
|
+
* @returns PnL in quote currency, or null if no active position
|
|
34683
|
+
*/
|
|
32763
34684
|
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
32764
34685
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
32765
34686
|
symbol,
|
|
@@ -32779,6 +34700,18 @@ class BacktestUtils {
|
|
|
32779
34700
|
}
|
|
32780
34701
|
return await bt.strategyCoreService.getPositionPnlCost(true, symbol, currentPrice, context);
|
|
32781
34702
|
};
|
|
34703
|
+
/**
|
|
34704
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
34705
|
+
*
|
|
34706
|
+
* The first element is always the original priceOpen (initial entry).
|
|
34707
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
34708
|
+
* Returns null if no pending signal exists.
|
|
34709
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
34710
|
+
*
|
|
34711
|
+
* @param symbol - Trading pair symbol
|
|
34712
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34713
|
+
* @returns Array of entry prices, or null if no active position
|
|
34714
|
+
*/
|
|
32782
34715
|
this.getPositionLevels = async (symbol, context) => {
|
|
32783
34716
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
32784
34717
|
symbol,
|
|
@@ -32797,6 +34730,25 @@ class BacktestUtils {
|
|
|
32797
34730
|
}
|
|
32798
34731
|
return await bt.strategyCoreService.getPositionLevels(true, symbol, context);
|
|
32799
34732
|
};
|
|
34733
|
+
/**
|
|
34734
|
+
* Returns the list of partial close events for the current pending signal.
|
|
34735
|
+
*
|
|
34736
|
+
* Each element represents a partial profit or loss close executed via
|
|
34737
|
+
* commitPartialProfit / commitPartialLoss (or their Cost variants).
|
|
34738
|
+
* Returns null if no pending signal exists.
|
|
34739
|
+
* Returns an empty array if no partials were executed yet.
|
|
34740
|
+
*
|
|
34741
|
+
* Each entry contains:
|
|
34742
|
+
* - `type` — "profit" or "loss"
|
|
34743
|
+
* - `percent` — percentage of position closed at this partial
|
|
34744
|
+
* - `currentPrice` — execution price of the partial close
|
|
34745
|
+
* - `costBasisAtClose` — accounting cost basis at the moment of this partial
|
|
34746
|
+
* - `entryCountAtClose` — number of DCA entries accumulated at this partial
|
|
34747
|
+
*
|
|
34748
|
+
* @param symbol - Trading pair symbol
|
|
34749
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
34750
|
+
* @returns Array of partial close records, or null if no active position
|
|
34751
|
+
*/
|
|
32800
34752
|
this.getPositionPartials = async (symbol, context) => {
|
|
32801
34753
|
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
32802
34754
|
symbol,
|
|
@@ -32985,6 +34937,28 @@ class BacktestUtils {
|
|
|
32985
34937
|
actions &&
|
|
32986
34938
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT));
|
|
32987
34939
|
}
|
|
34940
|
+
const investedCostForProfit = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
34941
|
+
if (investedCostForProfit === null) {
|
|
34942
|
+
return false;
|
|
34943
|
+
}
|
|
34944
|
+
const signalForProfit = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
34945
|
+
if (!signalForProfit) {
|
|
34946
|
+
return false;
|
|
34947
|
+
}
|
|
34948
|
+
if (await not(bt.strategyCoreService.validatePartialProfit(true, symbol, percentToClose, currentPrice, context))) {
|
|
34949
|
+
return false;
|
|
34950
|
+
}
|
|
34951
|
+
await Broker.commitPartialProfit({
|
|
34952
|
+
symbol,
|
|
34953
|
+
percentToClose,
|
|
34954
|
+
cost: percentToCloseCost(percentToClose, investedCostForProfit),
|
|
34955
|
+
currentPrice,
|
|
34956
|
+
position: signalForProfit.position,
|
|
34957
|
+
priceTakeProfit: signalForProfit.priceTakeProfit,
|
|
34958
|
+
priceStopLoss: signalForProfit.priceStopLoss,
|
|
34959
|
+
context,
|
|
34960
|
+
backtest: true,
|
|
34961
|
+
});
|
|
32988
34962
|
return await bt.strategyCoreService.partialProfit(true, symbol, percentToClose, currentPrice, context);
|
|
32989
34963
|
};
|
|
32990
34964
|
/**
|
|
@@ -33034,6 +35008,28 @@ class BacktestUtils {
|
|
|
33034
35008
|
actions &&
|
|
33035
35009
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_LOSS));
|
|
33036
35010
|
}
|
|
35011
|
+
const investedCostForLoss = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
35012
|
+
if (investedCostForLoss === null) {
|
|
35013
|
+
return false;
|
|
35014
|
+
}
|
|
35015
|
+
const signalForLoss = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35016
|
+
if (!signalForLoss) {
|
|
35017
|
+
return false;
|
|
35018
|
+
}
|
|
35019
|
+
if (await not(bt.strategyCoreService.validatePartialLoss(true, symbol, percentToClose, currentPrice, context))) {
|
|
35020
|
+
return false;
|
|
35021
|
+
}
|
|
35022
|
+
await Broker.commitPartialLoss({
|
|
35023
|
+
symbol,
|
|
35024
|
+
percentToClose,
|
|
35025
|
+
cost: percentToCloseCost(percentToClose, investedCostForLoss),
|
|
35026
|
+
currentPrice,
|
|
35027
|
+
position: signalForLoss.position,
|
|
35028
|
+
priceTakeProfit: signalForLoss.priceTakeProfit,
|
|
35029
|
+
priceStopLoss: signalForLoss.priceStopLoss,
|
|
35030
|
+
context,
|
|
35031
|
+
backtest: true,
|
|
35032
|
+
});
|
|
33037
35033
|
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
33038
35034
|
};
|
|
33039
35035
|
/**
|
|
@@ -33085,9 +35081,28 @@ class BacktestUtils {
|
|
|
33085
35081
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
33086
35082
|
}
|
|
33087
35083
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
33088
|
-
if (investedCost === null)
|
|
35084
|
+
if (investedCost === null) {
|
|
35085
|
+
return false;
|
|
35086
|
+
}
|
|
35087
|
+
const signalForProfitCost = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35088
|
+
if (!signalForProfitCost) {
|
|
33089
35089
|
return false;
|
|
35090
|
+
}
|
|
33090
35091
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
35092
|
+
if (await not(bt.strategyCoreService.validatePartialProfit(true, symbol, percentToClose, currentPrice, context))) {
|
|
35093
|
+
return false;
|
|
35094
|
+
}
|
|
35095
|
+
await Broker.commitPartialProfit({
|
|
35096
|
+
symbol,
|
|
35097
|
+
percentToClose,
|
|
35098
|
+
cost: dollarAmount,
|
|
35099
|
+
currentPrice,
|
|
35100
|
+
position: signalForProfitCost.position,
|
|
35101
|
+
priceTakeProfit: signalForProfitCost.priceTakeProfit,
|
|
35102
|
+
priceStopLoss: signalForProfitCost.priceStopLoss,
|
|
35103
|
+
context,
|
|
35104
|
+
backtest: true,
|
|
35105
|
+
});
|
|
33091
35106
|
return await bt.strategyCoreService.partialProfit(true, symbol, percentToClose, currentPrice, context);
|
|
33092
35107
|
};
|
|
33093
35108
|
/**
|
|
@@ -33139,9 +35154,28 @@ class BacktestUtils {
|
|
|
33139
35154
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
33140
35155
|
}
|
|
33141
35156
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(true, symbol, context);
|
|
33142
|
-
if (investedCost === null)
|
|
35157
|
+
if (investedCost === null) {
|
|
35158
|
+
return false;
|
|
35159
|
+
}
|
|
35160
|
+
const signalForLossCost = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35161
|
+
if (!signalForLossCost) {
|
|
33143
35162
|
return false;
|
|
35163
|
+
}
|
|
33144
35164
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
35165
|
+
if (await not(bt.strategyCoreService.validatePartialLoss(true, symbol, percentToClose, currentPrice, context))) {
|
|
35166
|
+
return false;
|
|
35167
|
+
}
|
|
35168
|
+
await Broker.commitPartialLoss({
|
|
35169
|
+
symbol,
|
|
35170
|
+
percentToClose,
|
|
35171
|
+
cost: dollarAmount,
|
|
35172
|
+
currentPrice,
|
|
35173
|
+
position: signalForLossCost.position,
|
|
35174
|
+
priceTakeProfit: signalForLossCost.priceTakeProfit,
|
|
35175
|
+
priceStopLoss: signalForLossCost.priceStopLoss,
|
|
35176
|
+
context,
|
|
35177
|
+
backtest: true,
|
|
35178
|
+
});
|
|
33145
35179
|
return await bt.strategyCoreService.partialLoss(true, symbol, percentToClose, currentPrice, context);
|
|
33146
35180
|
};
|
|
33147
35181
|
/**
|
|
@@ -33206,6 +35240,26 @@ class BacktestUtils {
|
|
|
33206
35240
|
actions &&
|
|
33207
35241
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_STOP));
|
|
33208
35242
|
}
|
|
35243
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35244
|
+
if (!signal) {
|
|
35245
|
+
return false;
|
|
35246
|
+
}
|
|
35247
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35248
|
+
if (effectivePriceOpen === null) {
|
|
35249
|
+
return false;
|
|
35250
|
+
}
|
|
35251
|
+
if (await not(bt.strategyCoreService.validateTrailingStop(true, symbol, percentShift, currentPrice, context))) {
|
|
35252
|
+
return false;
|
|
35253
|
+
}
|
|
35254
|
+
await Broker.commitTrailingStop({
|
|
35255
|
+
symbol,
|
|
35256
|
+
percentShift,
|
|
35257
|
+
currentPrice,
|
|
35258
|
+
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
35259
|
+
position: signal.position,
|
|
35260
|
+
context,
|
|
35261
|
+
backtest: true,
|
|
35262
|
+
});
|
|
33209
35263
|
return await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
|
|
33210
35264
|
};
|
|
33211
35265
|
/**
|
|
@@ -33270,6 +35324,132 @@ class BacktestUtils {
|
|
|
33270
35324
|
actions &&
|
|
33271
35325
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_PROFIT));
|
|
33272
35326
|
}
|
|
35327
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35328
|
+
if (!signal) {
|
|
35329
|
+
return false;
|
|
35330
|
+
}
|
|
35331
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35332
|
+
if (effectivePriceOpen === null) {
|
|
35333
|
+
return false;
|
|
35334
|
+
}
|
|
35335
|
+
if (await not(bt.strategyCoreService.validateTrailingTake(true, symbol, percentShift, currentPrice, context))) {
|
|
35336
|
+
return false;
|
|
35337
|
+
}
|
|
35338
|
+
await Broker.commitTrailingTake({
|
|
35339
|
+
symbol,
|
|
35340
|
+
percentShift,
|
|
35341
|
+
currentPrice,
|
|
35342
|
+
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
35343
|
+
position: signal.position,
|
|
35344
|
+
context,
|
|
35345
|
+
backtest: true,
|
|
35346
|
+
});
|
|
35347
|
+
return await bt.strategyCoreService.trailingTake(true, symbol, percentShift, currentPrice, context);
|
|
35348
|
+
};
|
|
35349
|
+
/**
|
|
35350
|
+
* Adjusts the trailing stop-loss to an absolute price level.
|
|
35351
|
+
*
|
|
35352
|
+
* Convenience wrapper around commitTrailingStop that converts an absolute
|
|
35353
|
+
* stop-loss price to a percentShift relative to the ORIGINAL SL distance.
|
|
35354
|
+
*
|
|
35355
|
+
* @param symbol - Trading pair symbol
|
|
35356
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
35357
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
35358
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35359
|
+
* @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
|
|
35360
|
+
*/
|
|
35361
|
+
this.commitTrailingStopCost = async (symbol, newStopLossPrice, currentPrice, context) => {
|
|
35362
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_STOP_COST, {
|
|
35363
|
+
symbol,
|
|
35364
|
+
newStopLossPrice,
|
|
35365
|
+
currentPrice,
|
|
35366
|
+
context,
|
|
35367
|
+
});
|
|
35368
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST);
|
|
35369
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST);
|
|
35370
|
+
{
|
|
35371
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35372
|
+
riskName &&
|
|
35373
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST);
|
|
35374
|
+
riskList &&
|
|
35375
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST));
|
|
35376
|
+
actions &&
|
|
35377
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_STOP_COST));
|
|
35378
|
+
}
|
|
35379
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35380
|
+
if (!signal) {
|
|
35381
|
+
return false;
|
|
35382
|
+
}
|
|
35383
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35384
|
+
if (effectivePriceOpen === null) {
|
|
35385
|
+
return false;
|
|
35386
|
+
}
|
|
35387
|
+
const percentShift = slPriceToPercentShift(newStopLossPrice, signal.priceStopLoss, effectivePriceOpen);
|
|
35388
|
+
if (await not(bt.strategyCoreService.validateTrailingStop(true, symbol, percentShift, currentPrice, context))) {
|
|
35389
|
+
return false;
|
|
35390
|
+
}
|
|
35391
|
+
await Broker.commitTrailingStop({
|
|
35392
|
+
symbol,
|
|
35393
|
+
percentShift,
|
|
35394
|
+
currentPrice,
|
|
35395
|
+
newStopLossPrice,
|
|
35396
|
+
position: signal.position,
|
|
35397
|
+
context,
|
|
35398
|
+
backtest: true,
|
|
35399
|
+
});
|
|
35400
|
+
return await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
|
|
35401
|
+
};
|
|
35402
|
+
/**
|
|
35403
|
+
* Adjusts the trailing take-profit to an absolute price level.
|
|
35404
|
+
*
|
|
35405
|
+
* Convenience wrapper around commitTrailingTake that converts an absolute
|
|
35406
|
+
* take-profit price to a percentShift relative to the ORIGINAL TP distance.
|
|
35407
|
+
*
|
|
35408
|
+
* @param symbol - Trading pair symbol
|
|
35409
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
35410
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
35411
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35412
|
+
* @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
|
|
35413
|
+
*/
|
|
35414
|
+
this.commitTrailingTakeCost = async (symbol, newTakeProfitPrice, currentPrice, context) => {
|
|
35415
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST, {
|
|
35416
|
+
symbol,
|
|
35417
|
+
newTakeProfitPrice,
|
|
35418
|
+
currentPrice,
|
|
35419
|
+
context,
|
|
35420
|
+
});
|
|
35421
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
35422
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
35423
|
+
{
|
|
35424
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35425
|
+
riskName &&
|
|
35426
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
35427
|
+
riskList &&
|
|
35428
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
35429
|
+
actions &&
|
|
35430
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
35431
|
+
}
|
|
35432
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35433
|
+
if (!signal) {
|
|
35434
|
+
return false;
|
|
35435
|
+
}
|
|
35436
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35437
|
+
if (effectivePriceOpen === null) {
|
|
35438
|
+
return false;
|
|
35439
|
+
}
|
|
35440
|
+
const percentShift = tpPriceToPercentShift(newTakeProfitPrice, signal.priceTakeProfit, effectivePriceOpen);
|
|
35441
|
+
if (await not(bt.strategyCoreService.validateTrailingTake(true, symbol, percentShift, currentPrice, context))) {
|
|
35442
|
+
return false;
|
|
35443
|
+
}
|
|
35444
|
+
await Broker.commitTrailingTake({
|
|
35445
|
+
symbol,
|
|
35446
|
+
percentShift,
|
|
35447
|
+
currentPrice,
|
|
35448
|
+
newTakeProfitPrice,
|
|
35449
|
+
position: signal.position,
|
|
35450
|
+
context,
|
|
35451
|
+
backtest: true,
|
|
35452
|
+
});
|
|
33273
35453
|
return await bt.strategyCoreService.trailingTake(true, symbol, percentShift, currentPrice, context);
|
|
33274
35454
|
};
|
|
33275
35455
|
/**
|
|
@@ -33310,6 +35490,26 @@ class BacktestUtils {
|
|
|
33310
35490
|
actions &&
|
|
33311
35491
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_BREAKEVEN));
|
|
33312
35492
|
}
|
|
35493
|
+
const signal = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35494
|
+
if (!signal) {
|
|
35495
|
+
return false;
|
|
35496
|
+
}
|
|
35497
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
|
|
35498
|
+
if (effectivePriceOpen === null) {
|
|
35499
|
+
return false;
|
|
35500
|
+
}
|
|
35501
|
+
if (await not(bt.strategyCoreService.validateBreakeven(true, symbol, currentPrice, context))) {
|
|
35502
|
+
return false;
|
|
35503
|
+
}
|
|
35504
|
+
await Broker.commitBreakeven({
|
|
35505
|
+
symbol,
|
|
35506
|
+
currentPrice,
|
|
35507
|
+
newStopLossPrice: breakevenNewStopLossPrice(effectivePriceOpen),
|
|
35508
|
+
newTakeProfitPrice: breakevenNewTakeProfitPrice(signal.priceTakeProfit, signal._trailingPriceTakeProfit),
|
|
35509
|
+
position: signal.position,
|
|
35510
|
+
context,
|
|
35511
|
+
backtest: true,
|
|
35512
|
+
});
|
|
33313
35513
|
return await bt.strategyCoreService.breakeven(true, symbol, currentPrice, context);
|
|
33314
35514
|
};
|
|
33315
35515
|
/**
|
|
@@ -33393,6 +35593,23 @@ class BacktestUtils {
|
|
|
33393
35593
|
actions &&
|
|
33394
35594
|
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_AVERAGE_BUY));
|
|
33395
35595
|
}
|
|
35596
|
+
if (await not(bt.strategyCoreService.validateAverageBuy(true, symbol, currentPrice, context))) {
|
|
35597
|
+
return false;
|
|
35598
|
+
}
|
|
35599
|
+
const signalForAvgBuy = await bt.strategyCoreService.getPendingSignal(true, symbol, currentPrice, context);
|
|
35600
|
+
if (!signalForAvgBuy) {
|
|
35601
|
+
return false;
|
|
35602
|
+
}
|
|
35603
|
+
await Broker.commitAverageBuy({
|
|
35604
|
+
symbol,
|
|
35605
|
+
currentPrice,
|
|
35606
|
+
cost,
|
|
35607
|
+
position: signalForAvgBuy.position,
|
|
35608
|
+
priceTakeProfit: signalForAvgBuy.priceTakeProfit,
|
|
35609
|
+
priceStopLoss: signalForAvgBuy.priceStopLoss,
|
|
35610
|
+
context,
|
|
35611
|
+
backtest: true,
|
|
35612
|
+
});
|
|
33396
35613
|
return await bt.strategyCoreService.averageBuy(true, symbol, currentPrice, context, cost);
|
|
33397
35614
|
};
|
|
33398
35615
|
/**
|
|
@@ -33581,6 +35798,8 @@ const LIVE_METHOD_NAME_PARTIAL_PROFIT_COST = "LiveUtils.commitPartialProfitCost"
|
|
|
33581
35798
|
const LIVE_METHOD_NAME_PARTIAL_LOSS_COST = "LiveUtils.commitPartialLossCost";
|
|
33582
35799
|
const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
|
|
33583
35800
|
const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
|
|
35801
|
+
const LIVE_METHOD_NAME_TRAILING_STOP_COST = "LiveUtils.commitTrailingStopCost";
|
|
35802
|
+
const LIVE_METHOD_NAME_TRAILING_PROFIT_COST = "LiveUtils.commitTrailingTakeCost";
|
|
33584
35803
|
const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
|
|
33585
35804
|
const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
|
|
33586
35805
|
/**
|
|
@@ -33707,43 +35926,82 @@ class LiveInstance {
|
|
|
33707
35926
|
context,
|
|
33708
35927
|
});
|
|
33709
35928
|
{
|
|
33710
|
-
bt.backtestMarkdownService.clear({
|
|
33711
|
-
bt.liveMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33712
|
-
bt.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33713
|
-
bt.performanceMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33714
|
-
bt.partialMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33715
|
-
bt.riskMarkdownService.clear({ symbol, strategyName: context.strategyName, exchangeName: context.exchangeName, frameName: "", backtest: false });
|
|
33716
|
-
}
|
|
33717
|
-
{
|
|
33718
|
-
bt.strategyCoreService.clear({
|
|
35929
|
+
bt.backtestMarkdownService.clear({
|
|
33719
35930
|
symbol,
|
|
33720
35931
|
strategyName: context.strategyName,
|
|
33721
35932
|
exchangeName: context.exchangeName,
|
|
33722
35933
|
frameName: "",
|
|
33723
35934
|
backtest: false,
|
|
33724
35935
|
});
|
|
33725
|
-
|
|
33726
|
-
|
|
33727
|
-
|
|
33728
|
-
riskName && bt.riskGlobalService.clear({
|
|
33729
|
-
riskName,
|
|
35936
|
+
bt.liveMarkdownService.clear({
|
|
35937
|
+
symbol,
|
|
35938
|
+
strategyName: context.strategyName,
|
|
33730
35939
|
exchangeName: context.exchangeName,
|
|
33731
35940
|
frameName: "",
|
|
33732
|
-
backtest: false
|
|
35941
|
+
backtest: false,
|
|
33733
35942
|
});
|
|
33734
|
-
|
|
33735
|
-
|
|
35943
|
+
bt.scheduleMarkdownService.clear({
|
|
35944
|
+
symbol,
|
|
35945
|
+
strategyName: context.strategyName,
|
|
35946
|
+
exchangeName: context.exchangeName,
|
|
35947
|
+
frameName: "",
|
|
35948
|
+
backtest: false,
|
|
35949
|
+
});
|
|
35950
|
+
bt.performanceMarkdownService.clear({
|
|
35951
|
+
symbol,
|
|
35952
|
+
strategyName: context.strategyName,
|
|
33736
35953
|
exchangeName: context.exchangeName,
|
|
33737
35954
|
frameName: "",
|
|
33738
|
-
backtest: false
|
|
33739
|
-
})
|
|
33740
|
-
|
|
33741
|
-
|
|
35955
|
+
backtest: false,
|
|
35956
|
+
});
|
|
35957
|
+
bt.partialMarkdownService.clear({
|
|
35958
|
+
symbol,
|
|
35959
|
+
strategyName: context.strategyName,
|
|
35960
|
+
exchangeName: context.exchangeName,
|
|
35961
|
+
frameName: "",
|
|
35962
|
+
backtest: false,
|
|
35963
|
+
});
|
|
35964
|
+
bt.riskMarkdownService.clear({
|
|
35965
|
+
symbol,
|
|
33742
35966
|
strategyName: context.strategyName,
|
|
33743
35967
|
exchangeName: context.exchangeName,
|
|
33744
35968
|
frameName: "",
|
|
33745
|
-
backtest: false
|
|
33746
|
-
})
|
|
35969
|
+
backtest: false,
|
|
35970
|
+
});
|
|
35971
|
+
}
|
|
35972
|
+
{
|
|
35973
|
+
bt.strategyCoreService.clear({
|
|
35974
|
+
symbol,
|
|
35975
|
+
strategyName: context.strategyName,
|
|
35976
|
+
exchangeName: context.exchangeName,
|
|
35977
|
+
frameName: "",
|
|
35978
|
+
backtest: false,
|
|
35979
|
+
});
|
|
35980
|
+
}
|
|
35981
|
+
{
|
|
35982
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35983
|
+
riskName &&
|
|
35984
|
+
bt.riskGlobalService.clear({
|
|
35985
|
+
riskName,
|
|
35986
|
+
exchangeName: context.exchangeName,
|
|
35987
|
+
frameName: "",
|
|
35988
|
+
backtest: false,
|
|
35989
|
+
});
|
|
35990
|
+
riskList &&
|
|
35991
|
+
riskList.forEach((riskName) => bt.riskGlobalService.clear({
|
|
35992
|
+
riskName,
|
|
35993
|
+
exchangeName: context.exchangeName,
|
|
35994
|
+
frameName: "",
|
|
35995
|
+
backtest: false,
|
|
35996
|
+
}));
|
|
35997
|
+
actions &&
|
|
35998
|
+
actions.forEach((actionName) => bt.actionCoreService.clear({
|
|
35999
|
+
actionName,
|
|
36000
|
+
strategyName: context.strategyName,
|
|
36001
|
+
exchangeName: context.exchangeName,
|
|
36002
|
+
frameName: "",
|
|
36003
|
+
backtest: false,
|
|
36004
|
+
}));
|
|
33747
36005
|
}
|
|
33748
36006
|
return bt.liveCommandService.run(symbol, context);
|
|
33749
36007
|
};
|
|
@@ -33786,7 +36044,7 @@ class LiveInstance {
|
|
|
33786
36044
|
bt.strategyCoreService.stopStrategy(false, symbol, {
|
|
33787
36045
|
strategyName: context.strategyName,
|
|
33788
36046
|
exchangeName: context.exchangeName,
|
|
33789
|
-
frameName: ""
|
|
36047
|
+
frameName: "",
|
|
33790
36048
|
});
|
|
33791
36049
|
bt.strategyCoreService
|
|
33792
36050
|
.hasPendingSignal(false, symbol, {
|
|
@@ -33867,9 +36125,12 @@ class LiveUtils {
|
|
|
33867
36125
|
}
|
|
33868
36126
|
{
|
|
33869
36127
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33870
|
-
riskName &&
|
|
33871
|
-
|
|
33872
|
-
|
|
36128
|
+
riskName &&
|
|
36129
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_RUN);
|
|
36130
|
+
riskList &&
|
|
36131
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_RUN));
|
|
36132
|
+
actions &&
|
|
36133
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_RUN));
|
|
33873
36134
|
}
|
|
33874
36135
|
const instance = this._getInstance(symbol, context.strategyName, context.exchangeName);
|
|
33875
36136
|
return instance.run(symbol, context);
|
|
@@ -33900,9 +36161,12 @@ class LiveUtils {
|
|
|
33900
36161
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_BACKGROUND);
|
|
33901
36162
|
{
|
|
33902
36163
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33903
|
-
riskName &&
|
|
33904
|
-
|
|
33905
|
-
|
|
36164
|
+
riskName &&
|
|
36165
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BACKGROUND);
|
|
36166
|
+
riskList &&
|
|
36167
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BACKGROUND));
|
|
36168
|
+
actions &&
|
|
36169
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_BACKGROUND));
|
|
33906
36170
|
}
|
|
33907
36171
|
const instance = this._getInstance(symbol, context.strategyName, context.exchangeName);
|
|
33908
36172
|
return instance.background(symbol, context);
|
|
@@ -33932,9 +36196,12 @@ class LiveUtils {
|
|
|
33932
36196
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
33933
36197
|
{
|
|
33934
36198
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33935
|
-
riskName &&
|
|
33936
|
-
|
|
33937
|
-
|
|
36199
|
+
riskName &&
|
|
36200
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
36201
|
+
riskList &&
|
|
36202
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL));
|
|
36203
|
+
actions &&
|
|
36204
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL));
|
|
33938
36205
|
}
|
|
33939
36206
|
return await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
33940
36207
|
strategyName: context.strategyName,
|
|
@@ -33966,9 +36233,12 @@ class LiveUtils {
|
|
|
33966
36233
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED);
|
|
33967
36234
|
{
|
|
33968
36235
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
33969
|
-
riskName &&
|
|
33970
|
-
|
|
33971
|
-
|
|
36236
|
+
riskName &&
|
|
36237
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED);
|
|
36238
|
+
riskList &&
|
|
36239
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED));
|
|
36240
|
+
actions &&
|
|
36241
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED));
|
|
33972
36242
|
}
|
|
33973
36243
|
return await bt.strategyCoreService.getTotalPercentClosed(false, symbol, {
|
|
33974
36244
|
strategyName: context.strategyName,
|
|
@@ -33999,9 +36269,12 @@ class LiveUtils {
|
|
|
33999
36269
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED);
|
|
34000
36270
|
{
|
|
34001
36271
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34002
|
-
riskName &&
|
|
34003
|
-
|
|
34004
|
-
|
|
36272
|
+
riskName &&
|
|
36273
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED);
|
|
36274
|
+
riskList &&
|
|
36275
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED));
|
|
36276
|
+
actions &&
|
|
36277
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED));
|
|
34005
36278
|
}
|
|
34006
36279
|
return await bt.strategyCoreService.getTotalCostClosed(false, symbol, {
|
|
34007
36280
|
strategyName: context.strategyName,
|
|
@@ -34034,9 +36307,12 @@ class LiveUtils {
|
|
|
34034
36307
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
34035
36308
|
{
|
|
34036
36309
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34037
|
-
riskName &&
|
|
34038
|
-
|
|
34039
|
-
|
|
36310
|
+
riskName &&
|
|
36311
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
36312
|
+
riskList &&
|
|
36313
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL));
|
|
36314
|
+
actions &&
|
|
36315
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL));
|
|
34040
36316
|
}
|
|
34041
36317
|
return await bt.strategyCoreService.getScheduledSignal(false, symbol, currentPrice, {
|
|
34042
36318
|
strategyName: context.strategyName,
|
|
@@ -34077,9 +36353,12 @@ class LiveUtils {
|
|
|
34077
36353
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_BREAKEVEN);
|
|
34078
36354
|
{
|
|
34079
36355
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34080
|
-
riskName &&
|
|
34081
|
-
|
|
34082
|
-
|
|
36356
|
+
riskName &&
|
|
36357
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN);
|
|
36358
|
+
riskList &&
|
|
36359
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN));
|
|
36360
|
+
actions &&
|
|
36361
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_BREAKEVEN));
|
|
34083
36362
|
}
|
|
34084
36363
|
return await bt.strategyCoreService.getBreakeven(false, symbol, currentPrice, {
|
|
34085
36364
|
strategyName: context.strategyName,
|
|
@@ -34087,15 +36366,31 @@ class LiveUtils {
|
|
|
34087
36366
|
frameName: "",
|
|
34088
36367
|
});
|
|
34089
36368
|
};
|
|
36369
|
+
/**
|
|
36370
|
+
* Returns the effective (weighted average) entry price for the current pending signal.
|
|
36371
|
+
*
|
|
36372
|
+
* Accounts for all DCA entries via commitAverageBuy.
|
|
36373
|
+
* Returns null if no pending signal exists.
|
|
36374
|
+
*
|
|
36375
|
+
* @param symbol - Trading pair symbol
|
|
36376
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36377
|
+
* @returns Effective entry price, or null if no active position
|
|
36378
|
+
*/
|
|
34090
36379
|
this.getPositionAveragePrice = async (symbol, context) => {
|
|
34091
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
36380
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
|
|
36381
|
+
symbol,
|
|
36382
|
+
context,
|
|
36383
|
+
});
|
|
34092
36384
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
34093
36385
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
34094
36386
|
{
|
|
34095
36387
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34096
|
-
riskName &&
|
|
34097
|
-
|
|
34098
|
-
|
|
36388
|
+
riskName &&
|
|
36389
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE);
|
|
36390
|
+
riskList &&
|
|
36391
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
36392
|
+
actions &&
|
|
36393
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
|
|
34099
36394
|
}
|
|
34100
36395
|
return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
34101
36396
|
strategyName: context.strategyName,
|
|
@@ -34103,15 +36398,30 @@ class LiveUtils {
|
|
|
34103
36398
|
frameName: "",
|
|
34104
36399
|
});
|
|
34105
36400
|
};
|
|
36401
|
+
/**
|
|
36402
|
+
* Returns the total number of base-asset units currently held in the position.
|
|
36403
|
+
*
|
|
36404
|
+
* Includes units from all DCA entries. Returns null if no pending signal exists.
|
|
36405
|
+
*
|
|
36406
|
+
* @param symbol - Trading pair symbol
|
|
36407
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36408
|
+
* @returns Total units held, or null if no active position
|
|
36409
|
+
*/
|
|
34106
36410
|
this.getPositionInvestedCount = async (symbol, context) => {
|
|
34107
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
36411
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT, {
|
|
36412
|
+
symbol,
|
|
36413
|
+
context,
|
|
36414
|
+
});
|
|
34108
36415
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
34109
36416
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
34110
36417
|
{
|
|
34111
36418
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34112
|
-
riskName &&
|
|
34113
|
-
|
|
34114
|
-
|
|
36419
|
+
riskName &&
|
|
36420
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT);
|
|
36421
|
+
riskList &&
|
|
36422
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
36423
|
+
actions &&
|
|
36424
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT));
|
|
34115
36425
|
}
|
|
34116
36426
|
return await bt.strategyCoreService.getPositionInvestedCount(false, symbol, {
|
|
34117
36427
|
strategyName: context.strategyName,
|
|
@@ -34119,15 +36429,30 @@ class LiveUtils {
|
|
|
34119
36429
|
frameName: "",
|
|
34120
36430
|
});
|
|
34121
36431
|
};
|
|
36432
|
+
/**
|
|
36433
|
+
* Returns the total dollar cost invested in the current position.
|
|
36434
|
+
*
|
|
36435
|
+
* Sum of all entry costs across DCA entries. Returns null if no pending signal exists.
|
|
36436
|
+
*
|
|
36437
|
+
* @param symbol - Trading pair symbol
|
|
36438
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36439
|
+
* @returns Total invested cost in quote currency, or null if no active position
|
|
36440
|
+
*/
|
|
34122
36441
|
this.getPositionInvestedCost = async (symbol, context) => {
|
|
34123
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
36442
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST, {
|
|
36443
|
+
symbol,
|
|
36444
|
+
context,
|
|
36445
|
+
});
|
|
34124
36446
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
34125
36447
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
34126
36448
|
{
|
|
34127
36449
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34128
|
-
riskName &&
|
|
34129
|
-
|
|
34130
|
-
|
|
36450
|
+
riskName &&
|
|
36451
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST);
|
|
36452
|
+
riskList &&
|
|
36453
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
36454
|
+
actions &&
|
|
36455
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST));
|
|
34131
36456
|
}
|
|
34132
36457
|
return await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
34133
36458
|
strategyName: context.strategyName,
|
|
@@ -34135,15 +36460,33 @@ class LiveUtils {
|
|
|
34135
36460
|
frameName: "",
|
|
34136
36461
|
});
|
|
34137
36462
|
};
|
|
36463
|
+
/**
|
|
36464
|
+
* Returns the current unrealized PnL as a percentage of the invested cost.
|
|
36465
|
+
*
|
|
36466
|
+
* Calculated relative to the effective (weighted average) entry price.
|
|
36467
|
+
* Positive for profit, negative for loss. Returns null if no pending signal exists.
|
|
36468
|
+
*
|
|
36469
|
+
* @param symbol - Trading pair symbol
|
|
36470
|
+
* @param currentPrice - Current market price
|
|
36471
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36472
|
+
* @returns PnL percentage, or null if no active position
|
|
36473
|
+
*/
|
|
34138
36474
|
this.getPositionPnlPercent = async (symbol, currentPrice, context) => {
|
|
34139
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
36475
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT, {
|
|
36476
|
+
symbol,
|
|
36477
|
+
currentPrice,
|
|
36478
|
+
context,
|
|
36479
|
+
});
|
|
34140
36480
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
34141
36481
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
34142
36482
|
{
|
|
34143
36483
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34144
|
-
riskName &&
|
|
34145
|
-
|
|
34146
|
-
|
|
36484
|
+
riskName &&
|
|
36485
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT);
|
|
36486
|
+
riskList &&
|
|
36487
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
36488
|
+
actions &&
|
|
36489
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT));
|
|
34147
36490
|
}
|
|
34148
36491
|
return await bt.strategyCoreService.getPositionPnlPercent(false, symbol, currentPrice, {
|
|
34149
36492
|
strategyName: context.strategyName,
|
|
@@ -34151,15 +36494,33 @@ class LiveUtils {
|
|
|
34151
36494
|
frameName: "",
|
|
34152
36495
|
});
|
|
34153
36496
|
};
|
|
36497
|
+
/**
|
|
36498
|
+
* Returns the current unrealized PnL in quote currency (dollar amount).
|
|
36499
|
+
*
|
|
36500
|
+
* Calculated as (currentPrice - effectiveEntry) * units for LONG,
|
|
36501
|
+
* reversed for SHORT. Returns null if no pending signal exists.
|
|
36502
|
+
*
|
|
36503
|
+
* @param symbol - Trading pair symbol
|
|
36504
|
+
* @param currentPrice - Current market price
|
|
36505
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36506
|
+
* @returns PnL in quote currency, or null if no active position
|
|
36507
|
+
*/
|
|
34154
36508
|
this.getPositionPnlCost = async (symbol, currentPrice, context) => {
|
|
34155
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
36509
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PNL_COST, {
|
|
36510
|
+
symbol,
|
|
36511
|
+
currentPrice,
|
|
36512
|
+
context,
|
|
36513
|
+
});
|
|
34156
36514
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
34157
36515
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
34158
36516
|
{
|
|
34159
36517
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34160
|
-
riskName &&
|
|
34161
|
-
|
|
34162
|
-
|
|
36518
|
+
riskName &&
|
|
36519
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST);
|
|
36520
|
+
riskList &&
|
|
36521
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
36522
|
+
actions &&
|
|
36523
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PNL_COST));
|
|
34163
36524
|
}
|
|
34164
36525
|
return await bt.strategyCoreService.getPositionPnlCost(false, symbol, currentPrice, {
|
|
34165
36526
|
strategyName: context.strategyName,
|
|
@@ -34167,15 +36528,33 @@ class LiveUtils {
|
|
|
34167
36528
|
frameName: "",
|
|
34168
36529
|
});
|
|
34169
36530
|
};
|
|
36531
|
+
/**
|
|
36532
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
36533
|
+
*
|
|
36534
|
+
* The first element is always the original priceOpen (initial entry).
|
|
36535
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
36536
|
+
* Returns null if no pending signal exists.
|
|
36537
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
36538
|
+
*
|
|
36539
|
+
* @param symbol - Trading pair symbol
|
|
36540
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36541
|
+
* @returns Array of entry prices, or null if no active position
|
|
36542
|
+
*/
|
|
34170
36543
|
this.getPositionLevels = async (symbol, context) => {
|
|
34171
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
36544
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_LEVELS, {
|
|
36545
|
+
symbol,
|
|
36546
|
+
context,
|
|
36547
|
+
});
|
|
34172
36548
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
34173
36549
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
34174
36550
|
{
|
|
34175
36551
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34176
|
-
riskName &&
|
|
34177
|
-
|
|
34178
|
-
|
|
36552
|
+
riskName &&
|
|
36553
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS);
|
|
36554
|
+
riskList &&
|
|
36555
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
36556
|
+
actions &&
|
|
36557
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_LEVELS));
|
|
34179
36558
|
}
|
|
34180
36559
|
return await bt.strategyCoreService.getPositionLevels(false, symbol, {
|
|
34181
36560
|
strategyName: context.strategyName,
|
|
@@ -34183,15 +36562,40 @@ class LiveUtils {
|
|
|
34183
36562
|
frameName: "",
|
|
34184
36563
|
});
|
|
34185
36564
|
};
|
|
36565
|
+
/**
|
|
36566
|
+
* Returns the list of partial close events for the current pending signal.
|
|
36567
|
+
*
|
|
36568
|
+
* Each element represents a partial profit or loss close executed via
|
|
36569
|
+
* commitPartialProfit / commitPartialLoss (or their Cost variants).
|
|
36570
|
+
* Returns null if no pending signal exists.
|
|
36571
|
+
* Returns an empty array if no partials were executed yet.
|
|
36572
|
+
*
|
|
36573
|
+
* Each entry contains:
|
|
36574
|
+
* - `type` — "profit" or "loss"
|
|
36575
|
+
* - `percent` — percentage of position closed at this partial
|
|
36576
|
+
* - `currentPrice` — execution price of the partial close
|
|
36577
|
+
* - `costBasisAtClose` — accounting cost basis at the moment of this partial
|
|
36578
|
+
* - `entryCountAtClose` — number of DCA entries accumulated at this partial
|
|
36579
|
+
*
|
|
36580
|
+
* @param symbol - Trading pair symbol
|
|
36581
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
36582
|
+
* @returns Array of partial close records, or null if no active position
|
|
36583
|
+
*/
|
|
34186
36584
|
this.getPositionPartials = async (symbol, context) => {
|
|
34187
|
-
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
36585
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIALS, {
|
|
36586
|
+
symbol,
|
|
36587
|
+
context,
|
|
36588
|
+
});
|
|
34188
36589
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
34189
36590
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
34190
36591
|
{
|
|
34191
36592
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34192
|
-
riskName &&
|
|
34193
|
-
|
|
34194
|
-
|
|
36593
|
+
riskName &&
|
|
36594
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS);
|
|
36595
|
+
riskList &&
|
|
36596
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
36597
|
+
actions &&
|
|
36598
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIALS));
|
|
34195
36599
|
}
|
|
34196
36600
|
return await bt.strategyCoreService.getPositionPartials(false, symbol, {
|
|
34197
36601
|
strategyName: context.strategyName,
|
|
@@ -34225,9 +36629,12 @@ class LiveUtils {
|
|
|
34225
36629
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_STOP);
|
|
34226
36630
|
{
|
|
34227
36631
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34228
|
-
riskName &&
|
|
34229
|
-
|
|
34230
|
-
|
|
36632
|
+
riskName &&
|
|
36633
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_STOP);
|
|
36634
|
+
riskList &&
|
|
36635
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_STOP));
|
|
36636
|
+
actions &&
|
|
36637
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_STOP));
|
|
34231
36638
|
}
|
|
34232
36639
|
await bt.strategyCoreService.stopStrategy(false, symbol, {
|
|
34233
36640
|
strategyName: context.strategyName,
|
|
@@ -34268,9 +36675,12 @@ class LiveUtils {
|
|
|
34268
36675
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
34269
36676
|
{
|
|
34270
36677
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34271
|
-
riskName &&
|
|
34272
|
-
|
|
34273
|
-
|
|
36678
|
+
riskName &&
|
|
36679
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
|
|
36680
|
+
riskList &&
|
|
36681
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CANCEL_SCHEDULED));
|
|
36682
|
+
actions &&
|
|
36683
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_CANCEL_SCHEDULED));
|
|
34274
36684
|
}
|
|
34275
36685
|
await bt.strategyCoreService.cancelScheduled(false, symbol, {
|
|
34276
36686
|
strategyName: context.strategyName,
|
|
@@ -34309,9 +36719,12 @@ class LiveUtils {
|
|
|
34309
36719
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
34310
36720
|
{
|
|
34311
36721
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34312
|
-
riskName &&
|
|
34313
|
-
|
|
34314
|
-
|
|
36722
|
+
riskName &&
|
|
36723
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CLOSE_PENDING);
|
|
36724
|
+
riskList &&
|
|
36725
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CLOSE_PENDING));
|
|
36726
|
+
actions &&
|
|
36727
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_CLOSE_PENDING));
|
|
34315
36728
|
}
|
|
34316
36729
|
await bt.strategyCoreService.closePending(false, symbol, {
|
|
34317
36730
|
strategyName: context.strategyName,
|
|
@@ -34358,10 +36771,51 @@ class LiveUtils {
|
|
|
34358
36771
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
|
|
34359
36772
|
{
|
|
34360
36773
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34361
|
-
riskName &&
|
|
34362
|
-
|
|
34363
|
-
|
|
36774
|
+
riskName &&
|
|
36775
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
|
|
36776
|
+
riskList &&
|
|
36777
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT));
|
|
36778
|
+
actions &&
|
|
36779
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_PROFIT));
|
|
36780
|
+
}
|
|
36781
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
36782
|
+
strategyName: context.strategyName,
|
|
36783
|
+
exchangeName: context.exchangeName,
|
|
36784
|
+
frameName: "",
|
|
36785
|
+
});
|
|
36786
|
+
if (investedCost === null) {
|
|
36787
|
+
return false;
|
|
36788
|
+
}
|
|
36789
|
+
const signalForProfit = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
36790
|
+
strategyName: context.strategyName,
|
|
36791
|
+
exchangeName: context.exchangeName,
|
|
36792
|
+
frameName: "",
|
|
36793
|
+
});
|
|
36794
|
+
if (!signalForProfit) {
|
|
36795
|
+
return false;
|
|
36796
|
+
}
|
|
36797
|
+
if (await not(bt.strategyCoreService.validatePartialProfit(false, symbol, percentToClose, currentPrice, {
|
|
36798
|
+
strategyName: context.strategyName,
|
|
36799
|
+
exchangeName: context.exchangeName,
|
|
36800
|
+
frameName: "",
|
|
36801
|
+
}))) {
|
|
36802
|
+
return false;
|
|
34364
36803
|
}
|
|
36804
|
+
await Broker.commitPartialProfit({
|
|
36805
|
+
symbol,
|
|
36806
|
+
percentToClose,
|
|
36807
|
+
cost: percentToCloseCost(percentToClose, investedCost),
|
|
36808
|
+
currentPrice,
|
|
36809
|
+
position: signalForProfit.position,
|
|
36810
|
+
priceTakeProfit: signalForProfit.priceTakeProfit,
|
|
36811
|
+
priceStopLoss: signalForProfit.priceStopLoss,
|
|
36812
|
+
context: {
|
|
36813
|
+
strategyName: context.strategyName,
|
|
36814
|
+
exchangeName: context.exchangeName,
|
|
36815
|
+
frameName: "",
|
|
36816
|
+
},
|
|
36817
|
+
backtest: false,
|
|
36818
|
+
});
|
|
34365
36819
|
return await bt.strategyCoreService.partialProfit(false, symbol, percentToClose, currentPrice, {
|
|
34366
36820
|
strategyName: context.strategyName,
|
|
34367
36821
|
exchangeName: context.exchangeName,
|
|
@@ -34407,10 +36861,51 @@ class LiveUtils {
|
|
|
34407
36861
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS);
|
|
34408
36862
|
{
|
|
34409
36863
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34410
|
-
riskName &&
|
|
34411
|
-
|
|
34412
|
-
|
|
36864
|
+
riskName &&
|
|
36865
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS);
|
|
36866
|
+
riskList &&
|
|
36867
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS));
|
|
36868
|
+
actions &&
|
|
36869
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_LOSS));
|
|
36870
|
+
}
|
|
36871
|
+
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
36872
|
+
strategyName: context.strategyName,
|
|
36873
|
+
exchangeName: context.exchangeName,
|
|
36874
|
+
frameName: "",
|
|
36875
|
+
});
|
|
36876
|
+
if (investedCost === null) {
|
|
36877
|
+
return false;
|
|
36878
|
+
}
|
|
36879
|
+
const signalForLoss = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
36880
|
+
strategyName: context.strategyName,
|
|
36881
|
+
exchangeName: context.exchangeName,
|
|
36882
|
+
frameName: "",
|
|
36883
|
+
});
|
|
36884
|
+
if (!signalForLoss) {
|
|
36885
|
+
return false;
|
|
34413
36886
|
}
|
|
36887
|
+
if (await not(bt.strategyCoreService.validatePartialLoss(false, symbol, percentToClose, currentPrice, {
|
|
36888
|
+
strategyName: context.strategyName,
|
|
36889
|
+
exchangeName: context.exchangeName,
|
|
36890
|
+
frameName: "",
|
|
36891
|
+
}))) {
|
|
36892
|
+
return false;
|
|
36893
|
+
}
|
|
36894
|
+
await Broker.commitPartialLoss({
|
|
36895
|
+
symbol,
|
|
36896
|
+
percentToClose,
|
|
36897
|
+
cost: percentToCloseCost(percentToClose, investedCost),
|
|
36898
|
+
currentPrice,
|
|
36899
|
+
position: signalForLoss.position,
|
|
36900
|
+
priceTakeProfit: signalForLoss.priceTakeProfit,
|
|
36901
|
+
priceStopLoss: signalForLoss.priceStopLoss,
|
|
36902
|
+
context: {
|
|
36903
|
+
strategyName: context.strategyName,
|
|
36904
|
+
exchangeName: context.exchangeName,
|
|
36905
|
+
frameName: "",
|
|
36906
|
+
},
|
|
36907
|
+
backtest: false,
|
|
36908
|
+
});
|
|
34414
36909
|
return await bt.strategyCoreService.partialLoss(false, symbol, percentToClose, currentPrice, {
|
|
34415
36910
|
strategyName: context.strategyName,
|
|
34416
36911
|
exchangeName: context.exchangeName,
|
|
@@ -34447,23 +36942,62 @@ class LiveUtils {
|
|
|
34447
36942
|
* ```
|
|
34448
36943
|
*/
|
|
34449
36944
|
this.commitPartialProfitCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
34450
|
-
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_PROFIT_COST, {
|
|
36945
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_PROFIT_COST, {
|
|
36946
|
+
symbol,
|
|
36947
|
+
dollarAmount,
|
|
36948
|
+
currentPrice,
|
|
36949
|
+
context,
|
|
36950
|
+
});
|
|
34451
36951
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
34452
36952
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
34453
36953
|
{
|
|
34454
36954
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34455
|
-
riskName &&
|
|
34456
|
-
|
|
34457
|
-
|
|
36955
|
+
riskName &&
|
|
36956
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST);
|
|
36957
|
+
riskList &&
|
|
36958
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
36959
|
+
actions &&
|
|
36960
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_PROFIT_COST));
|
|
34458
36961
|
}
|
|
34459
36962
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
34460
36963
|
strategyName: context.strategyName,
|
|
34461
36964
|
exchangeName: context.exchangeName,
|
|
34462
36965
|
frameName: "",
|
|
34463
36966
|
});
|
|
34464
|
-
if (investedCost === null)
|
|
36967
|
+
if (investedCost === null) {
|
|
36968
|
+
return false;
|
|
36969
|
+
}
|
|
36970
|
+
const signalForProfitCost = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
36971
|
+
strategyName: context.strategyName,
|
|
36972
|
+
exchangeName: context.exchangeName,
|
|
36973
|
+
frameName: "",
|
|
36974
|
+
});
|
|
36975
|
+
if (!signalForProfitCost) {
|
|
34465
36976
|
return false;
|
|
36977
|
+
}
|
|
34466
36978
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
36979
|
+
if (await not(bt.strategyCoreService.validatePartialProfit(false, symbol, percentToClose, currentPrice, {
|
|
36980
|
+
strategyName: context.strategyName,
|
|
36981
|
+
exchangeName: context.exchangeName,
|
|
36982
|
+
frameName: "",
|
|
36983
|
+
}))) {
|
|
36984
|
+
return false;
|
|
36985
|
+
}
|
|
36986
|
+
await Broker.commitPartialProfit({
|
|
36987
|
+
symbol,
|
|
36988
|
+
percentToClose,
|
|
36989
|
+
cost: dollarAmount,
|
|
36990
|
+
currentPrice,
|
|
36991
|
+
position: signalForProfitCost.position,
|
|
36992
|
+
priceTakeProfit: signalForProfitCost.priceTakeProfit,
|
|
36993
|
+
priceStopLoss: signalForProfitCost.priceStopLoss,
|
|
36994
|
+
context: {
|
|
36995
|
+
strategyName: context.strategyName,
|
|
36996
|
+
exchangeName: context.exchangeName,
|
|
36997
|
+
frameName: "",
|
|
36998
|
+
},
|
|
36999
|
+
backtest: false,
|
|
37000
|
+
});
|
|
34467
37001
|
return await bt.strategyCoreService.partialProfit(false, symbol, percentToClose, currentPrice, {
|
|
34468
37002
|
strategyName: context.strategyName,
|
|
34469
37003
|
exchangeName: context.exchangeName,
|
|
@@ -34500,23 +37034,62 @@ class LiveUtils {
|
|
|
34500
37034
|
* ```
|
|
34501
37035
|
*/
|
|
34502
37036
|
this.commitPartialLossCost = async (symbol, dollarAmount, currentPrice, context) => {
|
|
34503
|
-
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_LOSS_COST, {
|
|
37037
|
+
bt.loggerService.info(LIVE_METHOD_NAME_PARTIAL_LOSS_COST, {
|
|
37038
|
+
symbol,
|
|
37039
|
+
dollarAmount,
|
|
37040
|
+
currentPrice,
|
|
37041
|
+
context,
|
|
37042
|
+
});
|
|
34504
37043
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
34505
37044
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
34506
37045
|
{
|
|
34507
37046
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34508
|
-
riskName &&
|
|
34509
|
-
|
|
34510
|
-
|
|
37047
|
+
riskName &&
|
|
37048
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST);
|
|
37049
|
+
riskList &&
|
|
37050
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
37051
|
+
actions &&
|
|
37052
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_PARTIAL_LOSS_COST));
|
|
34511
37053
|
}
|
|
34512
37054
|
const investedCost = await bt.strategyCoreService.getPositionInvestedCost(false, symbol, {
|
|
34513
37055
|
strategyName: context.strategyName,
|
|
34514
37056
|
exchangeName: context.exchangeName,
|
|
34515
37057
|
frameName: "",
|
|
34516
37058
|
});
|
|
34517
|
-
if (investedCost === null)
|
|
37059
|
+
if (investedCost === null) {
|
|
37060
|
+
return false;
|
|
37061
|
+
}
|
|
37062
|
+
const signalForLossCost = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37063
|
+
strategyName: context.strategyName,
|
|
37064
|
+
exchangeName: context.exchangeName,
|
|
37065
|
+
frameName: "",
|
|
37066
|
+
});
|
|
37067
|
+
if (!signalForLossCost) {
|
|
34518
37068
|
return false;
|
|
37069
|
+
}
|
|
34519
37070
|
const percentToClose = (dollarAmount / investedCost) * 100;
|
|
37071
|
+
if (await not(bt.strategyCoreService.validatePartialLoss(false, symbol, percentToClose, currentPrice, {
|
|
37072
|
+
strategyName: context.strategyName,
|
|
37073
|
+
exchangeName: context.exchangeName,
|
|
37074
|
+
frameName: "",
|
|
37075
|
+
}))) {
|
|
37076
|
+
return false;
|
|
37077
|
+
}
|
|
37078
|
+
await Broker.commitPartialLoss({
|
|
37079
|
+
symbol,
|
|
37080
|
+
percentToClose,
|
|
37081
|
+
cost: dollarAmount,
|
|
37082
|
+
currentPrice,
|
|
37083
|
+
position: signalForLossCost.position,
|
|
37084
|
+
priceTakeProfit: signalForLossCost.priceTakeProfit,
|
|
37085
|
+
priceStopLoss: signalForLossCost.priceStopLoss,
|
|
37086
|
+
context: {
|
|
37087
|
+
strategyName: context.strategyName,
|
|
37088
|
+
exchangeName: context.exchangeName,
|
|
37089
|
+
frameName: "",
|
|
37090
|
+
},
|
|
37091
|
+
backtest: false,
|
|
37092
|
+
});
|
|
34520
37093
|
return await bt.strategyCoreService.partialLoss(false, symbol, percentToClose, currentPrice, {
|
|
34521
37094
|
strategyName: context.strategyName,
|
|
34522
37095
|
exchangeName: context.exchangeName,
|
|
@@ -34577,10 +37150,45 @@ class LiveUtils {
|
|
|
34577
37150
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_STOP);
|
|
34578
37151
|
{
|
|
34579
37152
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34580
|
-
riskName &&
|
|
34581
|
-
|
|
34582
|
-
|
|
37153
|
+
riskName &&
|
|
37154
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP);
|
|
37155
|
+
riskList &&
|
|
37156
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP));
|
|
37157
|
+
actions &&
|
|
37158
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_STOP));
|
|
34583
37159
|
}
|
|
37160
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37161
|
+
strategyName: context.strategyName,
|
|
37162
|
+
exchangeName: context.exchangeName,
|
|
37163
|
+
frameName: "",
|
|
37164
|
+
});
|
|
37165
|
+
if (!signal) {
|
|
37166
|
+
return false;
|
|
37167
|
+
}
|
|
37168
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37169
|
+
strategyName: context.strategyName,
|
|
37170
|
+
exchangeName: context.exchangeName,
|
|
37171
|
+
frameName: "",
|
|
37172
|
+
});
|
|
37173
|
+
if (effectivePriceOpen === null) {
|
|
37174
|
+
return false;
|
|
37175
|
+
}
|
|
37176
|
+
if (await not(bt.strategyCoreService.validateTrailingStop(false, symbol, percentShift, currentPrice, {
|
|
37177
|
+
strategyName: context.strategyName,
|
|
37178
|
+
exchangeName: context.exchangeName,
|
|
37179
|
+
frameName: "",
|
|
37180
|
+
}))) {
|
|
37181
|
+
return false;
|
|
37182
|
+
}
|
|
37183
|
+
await Broker.commitTrailingStop({
|
|
37184
|
+
symbol,
|
|
37185
|
+
percentShift,
|
|
37186
|
+
currentPrice,
|
|
37187
|
+
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
37188
|
+
position: signal.position,
|
|
37189
|
+
context,
|
|
37190
|
+
backtest: false,
|
|
37191
|
+
});
|
|
34584
37192
|
return await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
|
|
34585
37193
|
strategyName: context.strategyName,
|
|
34586
37194
|
exchangeName: context.exchangeName,
|
|
@@ -34641,10 +37249,183 @@ class LiveUtils {
|
|
|
34641
37249
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_PROFIT);
|
|
34642
37250
|
{
|
|
34643
37251
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34644
|
-
riskName &&
|
|
34645
|
-
|
|
34646
|
-
|
|
37252
|
+
riskName &&
|
|
37253
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT);
|
|
37254
|
+
riskList &&
|
|
37255
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT));
|
|
37256
|
+
actions &&
|
|
37257
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_PROFIT));
|
|
37258
|
+
}
|
|
37259
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37260
|
+
strategyName: context.strategyName,
|
|
37261
|
+
exchangeName: context.exchangeName,
|
|
37262
|
+
frameName: "",
|
|
37263
|
+
});
|
|
37264
|
+
if (!signal) {
|
|
37265
|
+
return false;
|
|
37266
|
+
}
|
|
37267
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37268
|
+
strategyName: context.strategyName,
|
|
37269
|
+
exchangeName: context.exchangeName,
|
|
37270
|
+
frameName: "",
|
|
37271
|
+
});
|
|
37272
|
+
if (effectivePriceOpen === null) {
|
|
37273
|
+
return false;
|
|
34647
37274
|
}
|
|
37275
|
+
if (await not(bt.strategyCoreService.validateTrailingTake(false, symbol, percentShift, currentPrice, {
|
|
37276
|
+
strategyName: context.strategyName,
|
|
37277
|
+
exchangeName: context.exchangeName,
|
|
37278
|
+
frameName: "",
|
|
37279
|
+
}))) {
|
|
37280
|
+
return false;
|
|
37281
|
+
}
|
|
37282
|
+
await Broker.commitTrailingTake({
|
|
37283
|
+
symbol,
|
|
37284
|
+
percentShift,
|
|
37285
|
+
currentPrice,
|
|
37286
|
+
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
37287
|
+
position: signal.position,
|
|
37288
|
+
context,
|
|
37289
|
+
backtest: false,
|
|
37290
|
+
});
|
|
37291
|
+
return await bt.strategyCoreService.trailingTake(false, symbol, percentShift, currentPrice, {
|
|
37292
|
+
strategyName: context.strategyName,
|
|
37293
|
+
exchangeName: context.exchangeName,
|
|
37294
|
+
frameName: "",
|
|
37295
|
+
});
|
|
37296
|
+
};
|
|
37297
|
+
/**
|
|
37298
|
+
* Adjusts the trailing stop-loss to an absolute price level.
|
|
37299
|
+
*
|
|
37300
|
+
* Convenience wrapper around commitTrailingStop that converts an absolute
|
|
37301
|
+
* stop-loss price to a percentShift relative to the ORIGINAL SL distance.
|
|
37302
|
+
*
|
|
37303
|
+
* @param symbol - Trading pair symbol
|
|
37304
|
+
* @param newStopLossPrice - Desired absolute stop-loss price
|
|
37305
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
37306
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37307
|
+
* @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected
|
|
37308
|
+
*/
|
|
37309
|
+
this.commitTrailingStopCost = async (symbol, newStopLossPrice, currentPrice, context) => {
|
|
37310
|
+
bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_STOP_COST, {
|
|
37311
|
+
symbol,
|
|
37312
|
+
newStopLossPrice,
|
|
37313
|
+
currentPrice,
|
|
37314
|
+
context,
|
|
37315
|
+
});
|
|
37316
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_TRAILING_STOP_COST);
|
|
37317
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_STOP_COST);
|
|
37318
|
+
{
|
|
37319
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37320
|
+
riskName &&
|
|
37321
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP_COST);
|
|
37322
|
+
riskList &&
|
|
37323
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP_COST));
|
|
37324
|
+
actions &&
|
|
37325
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_STOP_COST));
|
|
37326
|
+
}
|
|
37327
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37328
|
+
strategyName: context.strategyName,
|
|
37329
|
+
exchangeName: context.exchangeName,
|
|
37330
|
+
frameName: "",
|
|
37331
|
+
});
|
|
37332
|
+
if (!signal) {
|
|
37333
|
+
return false;
|
|
37334
|
+
}
|
|
37335
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37336
|
+
strategyName: context.strategyName,
|
|
37337
|
+
exchangeName: context.exchangeName,
|
|
37338
|
+
frameName: "",
|
|
37339
|
+
});
|
|
37340
|
+
if (effectivePriceOpen === null) {
|
|
37341
|
+
return false;
|
|
37342
|
+
}
|
|
37343
|
+
const percentShift = slPriceToPercentShift(newStopLossPrice, signal.priceStopLoss, effectivePriceOpen);
|
|
37344
|
+
if (await not(bt.strategyCoreService.validateTrailingStop(false, symbol, percentShift, currentPrice, {
|
|
37345
|
+
strategyName: context.strategyName,
|
|
37346
|
+
exchangeName: context.exchangeName,
|
|
37347
|
+
frameName: "",
|
|
37348
|
+
}))) {
|
|
37349
|
+
return false;
|
|
37350
|
+
}
|
|
37351
|
+
await Broker.commitTrailingStop({
|
|
37352
|
+
symbol,
|
|
37353
|
+
percentShift,
|
|
37354
|
+
currentPrice,
|
|
37355
|
+
newStopLossPrice,
|
|
37356
|
+
position: signal.position,
|
|
37357
|
+
context,
|
|
37358
|
+
backtest: false,
|
|
37359
|
+
});
|
|
37360
|
+
return await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
|
|
37361
|
+
strategyName: context.strategyName,
|
|
37362
|
+
exchangeName: context.exchangeName,
|
|
37363
|
+
frameName: "",
|
|
37364
|
+
});
|
|
37365
|
+
};
|
|
37366
|
+
/**
|
|
37367
|
+
* Adjusts the trailing take-profit to an absolute price level.
|
|
37368
|
+
*
|
|
37369
|
+
* Convenience wrapper around commitTrailingTake that converts an absolute
|
|
37370
|
+
* take-profit price to a percentShift relative to the ORIGINAL TP distance.
|
|
37371
|
+
*
|
|
37372
|
+
* @param symbol - Trading pair symbol
|
|
37373
|
+
* @param newTakeProfitPrice - Desired absolute take-profit price
|
|
37374
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
37375
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37376
|
+
* @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected
|
|
37377
|
+
*/
|
|
37378
|
+
this.commitTrailingTakeCost = async (symbol, newTakeProfitPrice, currentPrice, context) => {
|
|
37379
|
+
bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_PROFIT_COST, {
|
|
37380
|
+
symbol,
|
|
37381
|
+
newTakeProfitPrice,
|
|
37382
|
+
currentPrice,
|
|
37383
|
+
context,
|
|
37384
|
+
});
|
|
37385
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
37386
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
37387
|
+
{
|
|
37388
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37389
|
+
riskName &&
|
|
37390
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST);
|
|
37391
|
+
riskList &&
|
|
37392
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
37393
|
+
actions &&
|
|
37394
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_TRAILING_PROFIT_COST));
|
|
37395
|
+
}
|
|
37396
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37397
|
+
strategyName: context.strategyName,
|
|
37398
|
+
exchangeName: context.exchangeName,
|
|
37399
|
+
frameName: "",
|
|
37400
|
+
});
|
|
37401
|
+
if (!signal) {
|
|
37402
|
+
return false;
|
|
37403
|
+
}
|
|
37404
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37405
|
+
strategyName: context.strategyName,
|
|
37406
|
+
exchangeName: context.exchangeName,
|
|
37407
|
+
frameName: "",
|
|
37408
|
+
});
|
|
37409
|
+
if (effectivePriceOpen === null) {
|
|
37410
|
+
return false;
|
|
37411
|
+
}
|
|
37412
|
+
const percentShift = tpPriceToPercentShift(newTakeProfitPrice, signal.priceTakeProfit, effectivePriceOpen);
|
|
37413
|
+
if (await not(bt.strategyCoreService.validateTrailingTake(false, symbol, percentShift, currentPrice, {
|
|
37414
|
+
strategyName: context.strategyName,
|
|
37415
|
+
exchangeName: context.exchangeName,
|
|
37416
|
+
frameName: "",
|
|
37417
|
+
}))) {
|
|
37418
|
+
return false;
|
|
37419
|
+
}
|
|
37420
|
+
await Broker.commitTrailingTake({
|
|
37421
|
+
symbol,
|
|
37422
|
+
percentShift,
|
|
37423
|
+
currentPrice,
|
|
37424
|
+
newTakeProfitPrice,
|
|
37425
|
+
position: signal.position,
|
|
37426
|
+
context,
|
|
37427
|
+
backtest: false,
|
|
37428
|
+
});
|
|
34648
37429
|
return await bt.strategyCoreService.trailingTake(false, symbol, percentShift, currentPrice, {
|
|
34649
37430
|
strategyName: context.strategyName,
|
|
34650
37431
|
exchangeName: context.exchangeName,
|
|
@@ -34682,10 +37463,49 @@ class LiveUtils {
|
|
|
34682
37463
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_BREAKEVEN);
|
|
34683
37464
|
{
|
|
34684
37465
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34685
|
-
riskName &&
|
|
34686
|
-
|
|
34687
|
-
|
|
37466
|
+
riskName &&
|
|
37467
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BREAKEVEN);
|
|
37468
|
+
riskList &&
|
|
37469
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_BREAKEVEN));
|
|
37470
|
+
actions &&
|
|
37471
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_BREAKEVEN));
|
|
37472
|
+
}
|
|
37473
|
+
const signal = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37474
|
+
strategyName: context.strategyName,
|
|
37475
|
+
exchangeName: context.exchangeName,
|
|
37476
|
+
frameName: "",
|
|
37477
|
+
});
|
|
37478
|
+
if (!signal) {
|
|
37479
|
+
return false;
|
|
37480
|
+
}
|
|
37481
|
+
const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
|
|
37482
|
+
strategyName: context.strategyName,
|
|
37483
|
+
exchangeName: context.exchangeName,
|
|
37484
|
+
frameName: "",
|
|
37485
|
+
});
|
|
37486
|
+
if (effectivePriceOpen === null) {
|
|
37487
|
+
return false;
|
|
34688
37488
|
}
|
|
37489
|
+
if (await not(bt.strategyCoreService.validateBreakeven(false, symbol, currentPrice, {
|
|
37490
|
+
strategyName: context.strategyName,
|
|
37491
|
+
exchangeName: context.exchangeName,
|
|
37492
|
+
frameName: "",
|
|
37493
|
+
}))) {
|
|
37494
|
+
return false;
|
|
37495
|
+
}
|
|
37496
|
+
await Broker.commitBreakeven({
|
|
37497
|
+
symbol,
|
|
37498
|
+
currentPrice,
|
|
37499
|
+
newStopLossPrice: breakevenNewStopLossPrice(effectivePriceOpen),
|
|
37500
|
+
newTakeProfitPrice: breakevenNewTakeProfitPrice(signal.priceTakeProfit, signal._trailingPriceTakeProfit),
|
|
37501
|
+
position: signal.position,
|
|
37502
|
+
context: {
|
|
37503
|
+
strategyName: context.strategyName,
|
|
37504
|
+
exchangeName: context.exchangeName,
|
|
37505
|
+
frameName: "",
|
|
37506
|
+
},
|
|
37507
|
+
backtest: false,
|
|
37508
|
+
});
|
|
34689
37509
|
return await bt.strategyCoreService.breakeven(false, symbol, currentPrice, {
|
|
34690
37510
|
strategyName: context.strategyName,
|
|
34691
37511
|
exchangeName: context.exchangeName,
|
|
@@ -34722,9 +37542,12 @@ class LiveUtils {
|
|
|
34722
37542
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
34723
37543
|
{
|
|
34724
37544
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34725
|
-
riskName &&
|
|
34726
|
-
|
|
34727
|
-
|
|
37545
|
+
riskName &&
|
|
37546
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
|
|
37547
|
+
riskList &&
|
|
37548
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
37549
|
+
actions &&
|
|
37550
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED));
|
|
34728
37551
|
}
|
|
34729
37552
|
await bt.strategyCoreService.activateScheduled(false, symbol, {
|
|
34730
37553
|
strategyName: context.strategyName,
|
|
@@ -34765,10 +37588,42 @@ class LiveUtils {
|
|
|
34765
37588
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_AVERAGE_BUY);
|
|
34766
37589
|
{
|
|
34767
37590
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34768
|
-
riskName &&
|
|
34769
|
-
|
|
34770
|
-
|
|
37591
|
+
riskName &&
|
|
37592
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY);
|
|
37593
|
+
riskList &&
|
|
37594
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY));
|
|
37595
|
+
actions &&
|
|
37596
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_AVERAGE_BUY));
|
|
37597
|
+
}
|
|
37598
|
+
if (await not(bt.strategyCoreService.validateAverageBuy(false, symbol, currentPrice, {
|
|
37599
|
+
strategyName: context.strategyName,
|
|
37600
|
+
exchangeName: context.exchangeName,
|
|
37601
|
+
frameName: "",
|
|
37602
|
+
}))) {
|
|
37603
|
+
return false;
|
|
37604
|
+
}
|
|
37605
|
+
const signalForAvgBuy = await bt.strategyCoreService.getPendingSignal(false, symbol, currentPrice, {
|
|
37606
|
+
strategyName: context.strategyName,
|
|
37607
|
+
exchangeName: context.exchangeName,
|
|
37608
|
+
frameName: "",
|
|
37609
|
+
});
|
|
37610
|
+
if (!signalForAvgBuy) {
|
|
37611
|
+
return false;
|
|
34771
37612
|
}
|
|
37613
|
+
await Broker.commitAverageBuy({
|
|
37614
|
+
symbol,
|
|
37615
|
+
currentPrice,
|
|
37616
|
+
cost,
|
|
37617
|
+
position: signalForAvgBuy.position,
|
|
37618
|
+
priceTakeProfit: signalForAvgBuy.priceTakeProfit,
|
|
37619
|
+
priceStopLoss: signalForAvgBuy.priceStopLoss,
|
|
37620
|
+
context: {
|
|
37621
|
+
strategyName: context.strategyName,
|
|
37622
|
+
exchangeName: context.exchangeName,
|
|
37623
|
+
frameName: "",
|
|
37624
|
+
},
|
|
37625
|
+
backtest: false,
|
|
37626
|
+
});
|
|
34772
37627
|
return await bt.strategyCoreService.averageBuy(false, symbol, currentPrice, {
|
|
34773
37628
|
strategyName: context.strategyName,
|
|
34774
37629
|
exchangeName: context.exchangeName,
|
|
@@ -34802,9 +37657,12 @@ class LiveUtils {
|
|
|
34802
37657
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_DATA);
|
|
34803
37658
|
{
|
|
34804
37659
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34805
|
-
riskName &&
|
|
34806
|
-
|
|
34807
|
-
|
|
37660
|
+
riskName &&
|
|
37661
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_DATA);
|
|
37662
|
+
riskList &&
|
|
37663
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_DATA));
|
|
37664
|
+
actions &&
|
|
37665
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_DATA));
|
|
34808
37666
|
}
|
|
34809
37667
|
return await bt.liveMarkdownService.getData(symbol, context.strategyName, context.exchangeName, "", false);
|
|
34810
37668
|
};
|
|
@@ -34836,9 +37694,12 @@ class LiveUtils {
|
|
|
34836
37694
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_REPORT);
|
|
34837
37695
|
{
|
|
34838
37696
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34839
|
-
riskName &&
|
|
34840
|
-
|
|
34841
|
-
|
|
37697
|
+
riskName &&
|
|
37698
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT);
|
|
37699
|
+
riskList &&
|
|
37700
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
|
|
37701
|
+
actions &&
|
|
37702
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_REPORT));
|
|
34842
37703
|
}
|
|
34843
37704
|
return await bt.liveMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, "", false, columns);
|
|
34844
37705
|
};
|
|
@@ -34878,9 +37739,12 @@ class LiveUtils {
|
|
|
34878
37739
|
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_DUMP);
|
|
34879
37740
|
{
|
|
34880
37741
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
34881
|
-
riskName &&
|
|
34882
|
-
|
|
34883
|
-
|
|
37742
|
+
riskName &&
|
|
37743
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP);
|
|
37744
|
+
riskList &&
|
|
37745
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
|
|
37746
|
+
actions &&
|
|
37747
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_DUMP));
|
|
34884
37748
|
}
|
|
34885
37749
|
await bt.liveMarkdownService.dump(symbol, context.strategyName, context.exchangeName, "", false, path, columns);
|
|
34886
37750
|
};
|
|
@@ -42468,4 +45332,4 @@ const percentValue = (yesterdayValue, todayValue) => {
|
|
|
42468
45332
|
return yesterdayValue / todayValue - 1;
|
|
42469
45333
|
};
|
|
42470
45334
|
|
|
42471
|
-
export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, stopStrategy, toProfitLossDto, validate, waitForCandle, warmCandles };
|
|
45335
|
+
export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
|