backtest-kit 4.0.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +1025 -146
- package/build/index.mjs +1025 -148
- package/package.json +1 -1
- package/types.d.ts +624 -30
package/build/index.cjs
CHANGED
|
@@ -89,6 +89,10 @@ const coreServices$1 = {
|
|
|
89
89
|
actionCoreService: Symbol('actionCoreService'),
|
|
90
90
|
frameCoreService: Symbol('frameCoreService'),
|
|
91
91
|
};
|
|
92
|
+
const metaServices$1 = {
|
|
93
|
+
priceMetaService: Symbol('priceMetaService'),
|
|
94
|
+
timeMetaService: Symbol('timeMetaService'),
|
|
95
|
+
};
|
|
92
96
|
const globalServices$1 = {
|
|
93
97
|
sizingGlobalService: Symbol('sizingGlobalService'),
|
|
94
98
|
riskGlobalService: Symbol('riskGlobalService'),
|
|
@@ -154,6 +158,7 @@ const TYPES = {
|
|
|
154
158
|
...connectionServices$1,
|
|
155
159
|
...schemaServices$1,
|
|
156
160
|
...coreServices$1,
|
|
161
|
+
...metaServices$1,
|
|
157
162
|
...globalServices$1,
|
|
158
163
|
...commandServices$1,
|
|
159
164
|
...logicPrivateServices$1,
|
|
@@ -470,9 +475,17 @@ const GLOBAL_CONFIG = {
|
|
|
470
475
|
* Allows to commitAverageBuy if currentPrice is not the lowest price since entry, but still lower than priceOpen.
|
|
471
476
|
* This can help improve average entry price in cases where price has rebounded after entry but is still below priceOpen, without waiting for a new lower price.
|
|
472
477
|
*
|
|
473
|
-
* Default:
|
|
478
|
+
* Default: false (DCA logic enabled only when antirecord is broken)
|
|
474
479
|
*/
|
|
475
480
|
CC_ENABLE_DCA_EVERYWHERE: false,
|
|
481
|
+
/**
|
|
482
|
+
* Enables PPPL (Partial Profit, Partial Loss) logic even if this breaks a direction of exits
|
|
483
|
+
* Allows to take partial profit or loss on a position even if it results in a mix of profit and loss exits
|
|
484
|
+
* This can help lock in profits or cut losses on part of the position without waiting for a perfect exit scenario.
|
|
485
|
+
*
|
|
486
|
+
* Default: false (PPPL logic is only applied when it does not break the direction of exits, ensuring clearer profit/loss outcomes)
|
|
487
|
+
*/
|
|
488
|
+
CC_ENABLE_PPPL_EVERYWHERE: false,
|
|
476
489
|
/**
|
|
477
490
|
* Cost of entering a position (in USD).
|
|
478
491
|
* This is used as a default value for calculating position size and risk management when cost data is not provided by the strategy
|
|
@@ -3550,19 +3563,6 @@ const beginTime = (run) => (...args) => {
|
|
|
3550
3563
|
return fn();
|
|
3551
3564
|
};
|
|
3552
3565
|
|
|
3553
|
-
/**
|
|
3554
|
-
* Retrieves the current timestamp for debugging purposes.
|
|
3555
|
-
* If an execution context is active (e.g., during a backtest), it returns the timestamp from the context to ensure consistency with the simulated time.
|
|
3556
|
-
* Can be empty (undefined) if not called from strategy async context, as it's intended for debugging and not critical for logic.
|
|
3557
|
-
* @return {number | undefined} The current timestamp in milliseconds from the execution context, or undefined if not available.
|
|
3558
|
-
*/
|
|
3559
|
-
const getDebugTimestamp = () => {
|
|
3560
|
-
if (ExecutionContextService.hasContext()) {
|
|
3561
|
-
return bt.executionContextService.context.when.getTime();
|
|
3562
|
-
}
|
|
3563
|
-
return undefined;
|
|
3564
|
-
};
|
|
3565
|
-
|
|
3566
3566
|
const INTERVAL_MINUTES$6 = {
|
|
3567
3567
|
"1m": 1,
|
|
3568
3568
|
"3m": 3,
|
|
@@ -4270,7 +4270,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4270
4270
|
pendingAt: currentTime, // Для immediate signal оба времени одинаковые
|
|
4271
4271
|
timestamp: currentTime,
|
|
4272
4272
|
_isScheduled: false,
|
|
4273
|
-
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4273
|
+
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4274
4274
|
};
|
|
4275
4275
|
// Валидируем сигнал перед возвратом
|
|
4276
4276
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -4294,7 +4294,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4294
4294
|
pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
|
|
4295
4295
|
timestamp: currentTime,
|
|
4296
4296
|
_isScheduled: true,
|
|
4297
|
-
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4297
|
+
_entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4298
4298
|
};
|
|
4299
4299
|
// Валидируем сигнал перед возвратом
|
|
4300
4300
|
VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
|
|
@@ -4314,7 +4314,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
4314
4314
|
pendingAt: currentTime, // Для immediate signal оба времени одинаковые
|
|
4315
4315
|
timestamp: currentTime,
|
|
4316
4316
|
_isScheduled: false,
|
|
4317
|
-
_entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4317
|
+
_entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
|
|
4318
4318
|
};
|
|
4319
4319
|
// Валидируем сигнал перед возвратом
|
|
4320
4320
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -4384,7 +4384,7 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
|
|
|
4384
4384
|
self.params.logger.debug("ClientStrategy dispose");
|
|
4385
4385
|
await self.params.onDispose(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName, self.params.method.context.frameName, self.params.execution.context.backtest);
|
|
4386
4386
|
};
|
|
4387
|
-
const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
4387
|
+
const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
|
|
4388
4388
|
// Initialize partial array if not present
|
|
4389
4389
|
if (!signal._partial)
|
|
4390
4390
|
signal._partial = [];
|
|
@@ -4413,7 +4413,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4413
4413
|
entryCountAtClose,
|
|
4414
4414
|
currentPrice,
|
|
4415
4415
|
costBasisAtClose: remainingCostBasis,
|
|
4416
|
-
|
|
4416
|
+
timestamp,
|
|
4417
4417
|
});
|
|
4418
4418
|
self.params.logger.info("PARTIAL_PROFIT_FN executed", {
|
|
4419
4419
|
signalId: signal.id,
|
|
@@ -4423,7 +4423,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4423
4423
|
});
|
|
4424
4424
|
return true;
|
|
4425
4425
|
};
|
|
4426
|
-
const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
4426
|
+
const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
|
|
4427
4427
|
// Initialize partial array if not present
|
|
4428
4428
|
if (!signal._partial)
|
|
4429
4429
|
signal._partial = [];
|
|
@@ -4451,7 +4451,7 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
4451
4451
|
currentPrice,
|
|
4452
4452
|
entryCountAtClose,
|
|
4453
4453
|
costBasisAtClose: remainingCostBasis,
|
|
4454
|
-
|
|
4454
|
+
timestamp,
|
|
4455
4455
|
});
|
|
4456
4456
|
self.params.logger.warn("PARTIAL_LOSS_FN executed", {
|
|
4457
4457
|
signalId: signal.id,
|
|
@@ -4848,10 +4848,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4848
4848
|
});
|
|
4849
4849
|
return true;
|
|
4850
4850
|
};
|
|
4851
|
-
const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
|
|
4851
|
+
const AVERAGE_BUY_FN = (self, signal, currentPrice, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
|
|
4852
4852
|
// Ensure _entry is initialized (handles signals loaded from disk without _entry)
|
|
4853
4853
|
if (!signal._entry || signal._entry.length === 0) {
|
|
4854
|
-
signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
|
|
4854
|
+
signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
|
|
4855
4855
|
}
|
|
4856
4856
|
if (signal.position === "long") {
|
|
4857
4857
|
// LONG: new entry must beat the all-time low — strictly below every prior entry price
|
|
@@ -4881,7 +4881,7 @@ const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSI
|
|
|
4881
4881
|
return false;
|
|
4882
4882
|
}
|
|
4883
4883
|
}
|
|
4884
|
-
signal._entry.push({ price: currentPrice, cost,
|
|
4884
|
+
signal._entry.push({ price: currentPrice, cost, timestamp });
|
|
4885
4885
|
self.params.logger.info("AVERAGE_BUY_FN executed", {
|
|
4886
4886
|
signalId: signal.id,
|
|
4887
4887
|
position: signal.position,
|
|
@@ -6666,16 +6666,16 @@ class ClientStrategy {
|
|
|
6666
6666
|
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
6667
6667
|
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
6668
6668
|
*/
|
|
6669
|
-
async getPositionEntries(symbol) {
|
|
6669
|
+
async getPositionEntries(symbol, timestamp) {
|
|
6670
6670
|
this.params.logger.debug("ClientStrategy getPositionEntries", { symbol });
|
|
6671
6671
|
if (!this._pendingSignal) {
|
|
6672
6672
|
return null;
|
|
6673
6673
|
}
|
|
6674
6674
|
const entries = this._pendingSignal._entry;
|
|
6675
6675
|
if (!entries || entries.length === 0) {
|
|
6676
|
-
return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST }];
|
|
6676
|
+
return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
|
|
6677
6677
|
}
|
|
6678
|
-
return entries.map(({ price, cost }) => ({ price, cost }));
|
|
6678
|
+
return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
|
|
6679
6679
|
}
|
|
6680
6680
|
/**
|
|
6681
6681
|
* Performs a single tick of strategy execution.
|
|
@@ -7448,10 +7448,12 @@ class ClientStrategy {
|
|
|
7448
7448
|
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7449
7449
|
return false;
|
|
7450
7450
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7451
|
-
if (
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7451
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7452
|
+
if (this._pendingSignal.position === "long" && currentPrice <= effectivePriceOpen)
|
|
7453
|
+
return false;
|
|
7454
|
+
if (this._pendingSignal.position === "short" && currentPrice >= effectivePriceOpen)
|
|
7455
|
+
return false;
|
|
7456
|
+
}
|
|
7455
7457
|
const effectiveTakeProfit = this._pendingSignal._trailingPriceTakeProfit ?? this._pendingSignal.priceTakeProfit;
|
|
7456
7458
|
if (this._pendingSignal.position === "long" && currentPrice >= effectiveTakeProfit)
|
|
7457
7459
|
return false;
|
|
@@ -7509,7 +7511,7 @@ class ClientStrategy {
|
|
|
7509
7511
|
* // success3 = false (skipped, would exceed 100%)
|
|
7510
7512
|
* ```
|
|
7511
7513
|
*/
|
|
7512
|
-
async partialProfit(symbol, percentToClose, currentPrice, backtest) {
|
|
7514
|
+
async partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp) {
|
|
7513
7515
|
this.params.logger.debug("ClientStrategy partialProfit", {
|
|
7514
7516
|
symbol,
|
|
7515
7517
|
percentToClose,
|
|
@@ -7535,7 +7537,7 @@ class ClientStrategy {
|
|
|
7535
7537
|
throw new Error(`ClientStrategy partialProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
7536
7538
|
}
|
|
7537
7539
|
// Validation: currentPrice must be moving toward TP (profit direction)
|
|
7538
|
-
{
|
|
7540
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7539
7541
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7540
7542
|
if (this._pendingSignal.position === "long") {
|
|
7541
7543
|
// For LONG: currentPrice must be higher than effectivePriceOpen (moving toward TP)
|
|
@@ -7573,7 +7575,7 @@ class ClientStrategy {
|
|
|
7573
7575
|
return false;
|
|
7574
7576
|
}
|
|
7575
7577
|
// Execute partial close logic
|
|
7576
|
-
const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice);
|
|
7578
|
+
const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
|
|
7577
7579
|
// If partial was not executed (exceeded 100%), return false without persistence
|
|
7578
7580
|
if (!wasExecuted) {
|
|
7579
7581
|
return false;
|
|
@@ -7629,10 +7631,12 @@ class ClientStrategy {
|
|
|
7629
7631
|
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
|
|
7630
7632
|
return false;
|
|
7631
7633
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7632
|
-
if (
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7634
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7635
|
+
if (this._pendingSignal.position === "long" && currentPrice >= effectivePriceOpen)
|
|
7636
|
+
return false;
|
|
7637
|
+
if (this._pendingSignal.position === "short" && currentPrice <= effectivePriceOpen)
|
|
7638
|
+
return false;
|
|
7639
|
+
}
|
|
7636
7640
|
const effectiveStopLoss = this._pendingSignal._trailingPriceStopLoss ?? this._pendingSignal.priceStopLoss;
|
|
7637
7641
|
if (this._pendingSignal.position === "long" && currentPrice <= effectiveStopLoss)
|
|
7638
7642
|
return false;
|
|
@@ -7690,7 +7694,7 @@ class ClientStrategy {
|
|
|
7690
7694
|
* // success3 = false (skipped, would exceed 100%)
|
|
7691
7695
|
* ```
|
|
7692
7696
|
*/
|
|
7693
|
-
async partialLoss(symbol, percentToClose, currentPrice, backtest) {
|
|
7697
|
+
async partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp) {
|
|
7694
7698
|
this.params.logger.debug("ClientStrategy partialLoss", {
|
|
7695
7699
|
symbol,
|
|
7696
7700
|
percentToClose,
|
|
@@ -7716,7 +7720,7 @@ class ClientStrategy {
|
|
|
7716
7720
|
throw new Error(`ClientStrategy partialLoss: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
7717
7721
|
}
|
|
7718
7722
|
// Validation: currentPrice must be moving toward SL (loss direction)
|
|
7719
|
-
{
|
|
7723
|
+
if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
|
|
7720
7724
|
const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
|
|
7721
7725
|
if (this._pendingSignal.position === "long") {
|
|
7722
7726
|
// For LONG: currentPrice must be lower than effectivePriceOpen (moving toward SL)
|
|
@@ -7754,7 +7758,7 @@ class ClientStrategy {
|
|
|
7754
7758
|
return false;
|
|
7755
7759
|
}
|
|
7756
7760
|
// Execute partial close logic
|
|
7757
|
-
const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice);
|
|
7761
|
+
const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
|
|
7758
7762
|
// If partial was not executed (exceeded 100%), return false without persistence
|
|
7759
7763
|
if (!wasExecuted) {
|
|
7760
7764
|
return false;
|
|
@@ -8510,7 +8514,7 @@ class ClientStrategy {
|
|
|
8510
8514
|
* @param backtest - Whether running in backtest mode
|
|
8511
8515
|
* @returns Promise<boolean> - true if entry added, false if rejected by direction check
|
|
8512
8516
|
*/
|
|
8513
|
-
async averageBuy(symbol, currentPrice, backtest, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
|
|
8517
|
+
async averageBuy(symbol, currentPrice, backtest, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
|
|
8514
8518
|
this.params.logger.debug("ClientStrategy averageBuy", {
|
|
8515
8519
|
symbol,
|
|
8516
8520
|
currentPrice,
|
|
@@ -8525,7 +8529,7 @@ class ClientStrategy {
|
|
|
8525
8529
|
throw new Error(`ClientStrategy averageBuy: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
8526
8530
|
}
|
|
8527
8531
|
// Execute averaging logic
|
|
8528
|
-
const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, cost);
|
|
8532
|
+
const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, timestamp, cost);
|
|
8529
8533
|
if (!result) {
|
|
8530
8534
|
return false;
|
|
8531
8535
|
}
|
|
@@ -8989,7 +8993,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
|
|
|
8989
8993
|
* @param backtest - Whether running in backtest mode
|
|
8990
8994
|
* @returns Unique string key for memoization
|
|
8991
8995
|
*/
|
|
8992
|
-
const CREATE_KEY_FN$
|
|
8996
|
+
const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
8993
8997
|
const parts = [symbol, strategyName, exchangeName];
|
|
8994
8998
|
if (frameName)
|
|
8995
8999
|
parts.push(frameName);
|
|
@@ -9166,6 +9170,8 @@ class StrategyConnectionService {
|
|
|
9166
9170
|
this.partialConnectionService = inject(TYPES.partialConnectionService);
|
|
9167
9171
|
this.breakevenConnectionService = inject(TYPES.breakevenConnectionService);
|
|
9168
9172
|
this.actionCoreService = inject(TYPES.actionCoreService);
|
|
9173
|
+
this.timeMetaService = inject(TYPES.timeMetaService);
|
|
9174
|
+
this.priceMetaService = inject(TYPES.priceMetaService);
|
|
9169
9175
|
/**
|
|
9170
9176
|
* Retrieves memoized ClientStrategy instance for given symbol-strategy pair with exchange and frame isolation.
|
|
9171
9177
|
*
|
|
@@ -9179,7 +9185,7 @@ class StrategyConnectionService {
|
|
|
9179
9185
|
* @param backtest - Whether running in backtest mode
|
|
9180
9186
|
* @returns Configured ClientStrategy instance
|
|
9181
9187
|
*/
|
|
9182
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
9188
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
9183
9189
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
9184
9190
|
return new ClientStrategy({
|
|
9185
9191
|
symbol,
|
|
@@ -9266,6 +9272,20 @@ class StrategyConnectionService {
|
|
|
9266
9272
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9267
9273
|
return await strategy.getTotalCostClosed(symbol);
|
|
9268
9274
|
};
|
|
9275
|
+
/**
|
|
9276
|
+
* Returns the effective (DCA-averaged) entry price for the current pending signal.
|
|
9277
|
+
*
|
|
9278
|
+
* This is the harmonic mean of all _entry prices, which is the correct
|
|
9279
|
+
* cost-basis price used in all PNL calculations.
|
|
9280
|
+
* With no DCA entries, equals the original priceOpen.
|
|
9281
|
+
*
|
|
9282
|
+
* Returns null if no pending signal exists.
|
|
9283
|
+
*
|
|
9284
|
+
* @param backtest - Whether running in backtest mode
|
|
9285
|
+
* @param symbol - Trading pair symbol
|
|
9286
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9287
|
+
* @returns Promise resolving to effective entry price or null
|
|
9288
|
+
*/
|
|
9269
9289
|
this.getPositionAveragePrice = async (backtest, symbol, context) => {
|
|
9270
9290
|
this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
|
|
9271
9291
|
symbol,
|
|
@@ -9275,6 +9295,19 @@ class StrategyConnectionService {
|
|
|
9275
9295
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9276
9296
|
return await strategy.getPositionAveragePrice(symbol);
|
|
9277
9297
|
};
|
|
9298
|
+
/**
|
|
9299
|
+
* Returns the number of DCA entries made for the current pending signal.
|
|
9300
|
+
*
|
|
9301
|
+
* 1 = original entry only (no DCA).
|
|
9302
|
+
* Increases by 1 with each successful commitAverageBuy().
|
|
9303
|
+
*
|
|
9304
|
+
* Returns null if no pending signal exists.
|
|
9305
|
+
*
|
|
9306
|
+
* @param backtest - Whether running in backtest mode
|
|
9307
|
+
* @param symbol - Trading pair symbol
|
|
9308
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9309
|
+
* @returns Promise resolving to entry count or null
|
|
9310
|
+
*/
|
|
9278
9311
|
this.getPositionInvestedCount = async (backtest, symbol, context) => {
|
|
9279
9312
|
this.loggerService.log("strategyConnectionService getPositionInvestedCount", {
|
|
9280
9313
|
symbol,
|
|
@@ -9284,6 +9317,19 @@ class StrategyConnectionService {
|
|
|
9284
9317
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9285
9318
|
return await strategy.getPositionInvestedCount(symbol);
|
|
9286
9319
|
};
|
|
9320
|
+
/**
|
|
9321
|
+
* Returns the total invested cost basis in dollars for the current pending signal.
|
|
9322
|
+
*
|
|
9323
|
+
* Equal to entryCount × $100 (COST_BASIS_PER_ENTRY).
|
|
9324
|
+
* 1 entry = $100, 2 entries = $200, etc.
|
|
9325
|
+
*
|
|
9326
|
+
* Returns null if no pending signal exists.
|
|
9327
|
+
*
|
|
9328
|
+
* @param backtest - Whether running in backtest mode
|
|
9329
|
+
* @param symbol - Trading pair symbol
|
|
9330
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9331
|
+
* @returns Promise resolving to total invested cost in dollars or null
|
|
9332
|
+
*/
|
|
9287
9333
|
this.getPositionInvestedCost = async (backtest, symbol, context) => {
|
|
9288
9334
|
this.loggerService.log("strategyConnectionService getPositionInvestedCost", {
|
|
9289
9335
|
symbol,
|
|
@@ -9293,6 +9339,20 @@ class StrategyConnectionService {
|
|
|
9293
9339
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9294
9340
|
return await strategy.getPositionInvestedCost(symbol);
|
|
9295
9341
|
};
|
|
9342
|
+
/**
|
|
9343
|
+
* Returns the unrealized PNL percentage for the current pending signal at currentPrice.
|
|
9344
|
+
*
|
|
9345
|
+
* Accounts for partial closes, DCA entries, slippage and fees
|
|
9346
|
+
* (delegates to toProfitLossDto).
|
|
9347
|
+
*
|
|
9348
|
+
* Returns null if no pending signal exists.
|
|
9349
|
+
*
|
|
9350
|
+
* @param backtest - Whether running in backtest mode
|
|
9351
|
+
* @param symbol - Trading pair symbol
|
|
9352
|
+
* @param currentPrice - Current market price
|
|
9353
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9354
|
+
* @returns Promise resolving to pnlPercentage or null
|
|
9355
|
+
*/
|
|
9296
9356
|
this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
|
|
9297
9357
|
this.loggerService.log("strategyConnectionService getPositionPnlPercent", {
|
|
9298
9358
|
symbol,
|
|
@@ -9303,6 +9363,20 @@ class StrategyConnectionService {
|
|
|
9303
9363
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9304
9364
|
return await strategy.getPositionPnlPercent(symbol, currentPrice);
|
|
9305
9365
|
};
|
|
9366
|
+
/**
|
|
9367
|
+
* Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
|
|
9368
|
+
*
|
|
9369
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost
|
|
9370
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
9371
|
+
*
|
|
9372
|
+
* Returns null if no pending signal exists.
|
|
9373
|
+
*
|
|
9374
|
+
* @param backtest - Whether running in backtest mode
|
|
9375
|
+
* @param symbol - Trading pair symbol
|
|
9376
|
+
* @param currentPrice - Current market price
|
|
9377
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9378
|
+
* @returns Promise resolving to pnl in dollars or null
|
|
9379
|
+
*/
|
|
9306
9380
|
this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
|
|
9307
9381
|
this.loggerService.log("strategyConnectionService getPositionPnlCost", {
|
|
9308
9382
|
symbol,
|
|
@@ -9313,6 +9387,27 @@ class StrategyConnectionService {
|
|
|
9313
9387
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9314
9388
|
return await strategy.getPositionPnlCost(symbol, currentPrice);
|
|
9315
9389
|
};
|
|
9390
|
+
/**
|
|
9391
|
+
* Returns the list of DCA entry prices for the current pending signal.
|
|
9392
|
+
*
|
|
9393
|
+
* The first element is always the original priceOpen (initial entry).
|
|
9394
|
+
* Each subsequent element is a price added by commitAverageBuy().
|
|
9395
|
+
*
|
|
9396
|
+
* Returns null if no pending signal exists.
|
|
9397
|
+
* Returns a single-element array [priceOpen] if no DCA entries were made.
|
|
9398
|
+
*
|
|
9399
|
+
* @param backtest - Whether running in backtest mode
|
|
9400
|
+
* @param symbol - Trading pair symbol
|
|
9401
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9402
|
+
* @returns Promise resolving to array of entry prices or null
|
|
9403
|
+
*
|
|
9404
|
+
* @example
|
|
9405
|
+
* ```typescript
|
|
9406
|
+
* // No DCA: [43000]
|
|
9407
|
+
* // One DCA: [43000, 42000]
|
|
9408
|
+
* // Two DCA: [43000, 42000, 41500]
|
|
9409
|
+
* ```
|
|
9410
|
+
*/
|
|
9316
9411
|
this.getPositionLevels = async (backtest, symbol, context) => {
|
|
9317
9412
|
this.loggerService.log("strategyConnectionService getPositionLevels", {
|
|
9318
9413
|
symbol,
|
|
@@ -9322,6 +9417,20 @@ class StrategyConnectionService {
|
|
|
9322
9417
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9323
9418
|
return await strategy.getPositionLevels(symbol);
|
|
9324
9419
|
};
|
|
9420
|
+
/**
|
|
9421
|
+
* Returns the list of partial closes for the current pending signal.
|
|
9422
|
+
*
|
|
9423
|
+
* Each entry records a partial profit or loss close event with its type,
|
|
9424
|
+
* percent closed, price at close, cost basis snapshot, and entry count at close.
|
|
9425
|
+
*
|
|
9426
|
+
* Returns null if no pending signal exists.
|
|
9427
|
+
* Returns an empty array if no partial closes have been executed.
|
|
9428
|
+
*
|
|
9429
|
+
* @param backtest - Whether running in backtest mode
|
|
9430
|
+
* @param symbol - Trading pair symbol
|
|
9431
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9432
|
+
* @returns Promise resolving to array of partial close records or null
|
|
9433
|
+
*/
|
|
9325
9434
|
this.getPositionPartials = async (backtest, symbol, context) => {
|
|
9326
9435
|
this.loggerService.log("strategyConnectionService getPositionPartials", {
|
|
9327
9436
|
symbol,
|
|
@@ -9331,6 +9440,27 @@ class StrategyConnectionService {
|
|
|
9331
9440
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9332
9441
|
return await strategy.getPositionPartials(symbol);
|
|
9333
9442
|
};
|
|
9443
|
+
/**
|
|
9444
|
+
* Returns the list of DCA entry prices and costs for the current pending signal.
|
|
9445
|
+
*
|
|
9446
|
+
* Each entry records the price and cost of a single position entry.
|
|
9447
|
+
* The first element is always the original priceOpen (initial entry).
|
|
9448
|
+
* Each subsequent element is an entry added by averageBuy().
|
|
9449
|
+
*
|
|
9450
|
+
* Returns null if no pending signal exists.
|
|
9451
|
+
* Returns a single-element array [{ price: priceOpen, cost }] if no DCA entries were made.
|
|
9452
|
+
*
|
|
9453
|
+
* @param backtest - Whether running in backtest mode
|
|
9454
|
+
* @param symbol - Trading pair symbol
|
|
9455
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9456
|
+
* @returns Promise resolving to array of entry records or null
|
|
9457
|
+
*
|
|
9458
|
+
* @example
|
|
9459
|
+
* ```typescript
|
|
9460
|
+
* // No DCA: [{ price: 43000, cost: 100 }]
|
|
9461
|
+
* // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
|
|
9462
|
+
* ```
|
|
9463
|
+
*/
|
|
9334
9464
|
this.getPositionEntries = async (backtest, symbol, context) => {
|
|
9335
9465
|
this.loggerService.log("strategyConnectionService getPositionEntries", {
|
|
9336
9466
|
symbol,
|
|
@@ -9338,7 +9468,8 @@ class StrategyConnectionService {
|
|
|
9338
9468
|
backtest,
|
|
9339
9469
|
});
|
|
9340
9470
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9341
|
-
|
|
9471
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9472
|
+
return await strategy.getPositionEntries(symbol, timestamp);
|
|
9342
9473
|
};
|
|
9343
9474
|
/**
|
|
9344
9475
|
* Retrieves the currently active scheduled signal for the strategy.
|
|
@@ -9440,6 +9571,10 @@ class StrategyConnectionService {
|
|
|
9440
9571
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9441
9572
|
await strategy.waitForInit();
|
|
9442
9573
|
const tick = await strategy.tick(symbol, context.strategyName);
|
|
9574
|
+
{
|
|
9575
|
+
this.priceMetaService.next(symbol, tick.currentPrice, context, backtest);
|
|
9576
|
+
this.timeMetaService.next(symbol, tick.createdAt, context, backtest);
|
|
9577
|
+
}
|
|
9443
9578
|
{
|
|
9444
9579
|
await CALL_SIGNAL_EMIT_FN(this, tick, context, backtest, symbol);
|
|
9445
9580
|
}
|
|
@@ -9546,7 +9681,7 @@ class StrategyConnectionService {
|
|
|
9546
9681
|
}
|
|
9547
9682
|
return;
|
|
9548
9683
|
}
|
|
9549
|
-
const key = CREATE_KEY_FN$
|
|
9684
|
+
const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
9550
9685
|
if (!this.getStrategy.has(key)) {
|
|
9551
9686
|
return;
|
|
9552
9687
|
}
|
|
@@ -9615,9 +9750,9 @@ class StrategyConnectionService {
|
|
|
9615
9750
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9616
9751
|
* @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
|
|
9617
9752
|
*/
|
|
9618
|
-
this.validatePartialProfit = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9753
|
+
this.validatePartialProfit = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9619
9754
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9620
|
-
return
|
|
9755
|
+
return await strategy.validatePartialProfit(symbol, percentToClose, currentPrice);
|
|
9621
9756
|
};
|
|
9622
9757
|
/**
|
|
9623
9758
|
* Executes partial close at profit level (moving toward TP).
|
|
@@ -9658,7 +9793,8 @@ class StrategyConnectionService {
|
|
|
9658
9793
|
backtest,
|
|
9659
9794
|
});
|
|
9660
9795
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9661
|
-
|
|
9796
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9797
|
+
return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp);
|
|
9662
9798
|
};
|
|
9663
9799
|
/**
|
|
9664
9800
|
* Checks whether `partialLoss` would succeed without executing it.
|
|
@@ -9671,9 +9807,9 @@ class StrategyConnectionService {
|
|
|
9671
9807
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9672
9808
|
* @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
|
|
9673
9809
|
*/
|
|
9674
|
-
this.validatePartialLoss = (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9810
|
+
this.validatePartialLoss = async (backtest, symbol, percentToClose, currentPrice, context) => {
|
|
9675
9811
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9676
|
-
return
|
|
9812
|
+
return await strategy.validatePartialLoss(symbol, percentToClose, currentPrice);
|
|
9677
9813
|
};
|
|
9678
9814
|
/**
|
|
9679
9815
|
* Executes partial close at loss level (moving toward SL).
|
|
@@ -9714,7 +9850,8 @@ class StrategyConnectionService {
|
|
|
9714
9850
|
backtest,
|
|
9715
9851
|
});
|
|
9716
9852
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9717
|
-
|
|
9853
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
9854
|
+
return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp);
|
|
9718
9855
|
};
|
|
9719
9856
|
/**
|
|
9720
9857
|
* Checks whether `trailingStop` would succeed without executing it.
|
|
@@ -9727,9 +9864,9 @@ class StrategyConnectionService {
|
|
|
9727
9864
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9728
9865
|
* @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
|
|
9729
9866
|
*/
|
|
9730
|
-
this.validateTrailingStop = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9867
|
+
this.validateTrailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9731
9868
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9732
|
-
return
|
|
9869
|
+
return await strategy.validateTrailingStop(symbol, percentShift, currentPrice);
|
|
9733
9870
|
};
|
|
9734
9871
|
/**
|
|
9735
9872
|
* Adjusts the trailing stop-loss distance for an active pending signal.
|
|
@@ -9781,9 +9918,9 @@ class StrategyConnectionService {
|
|
|
9781
9918
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9782
9919
|
* @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
|
|
9783
9920
|
*/
|
|
9784
|
-
this.validateTrailingTake = (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9921
|
+
this.validateTrailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
9785
9922
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9786
|
-
return
|
|
9923
|
+
return await strategy.validateTrailingTake(symbol, percentShift, currentPrice);
|
|
9787
9924
|
};
|
|
9788
9925
|
/**
|
|
9789
9926
|
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
@@ -9834,9 +9971,9 @@ class StrategyConnectionService {
|
|
|
9834
9971
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9835
9972
|
* @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
|
|
9836
9973
|
*/
|
|
9837
|
-
this.validateBreakeven = (backtest, symbol, currentPrice, context) => {
|
|
9974
|
+
this.validateBreakeven = async (backtest, symbol, currentPrice, context) => {
|
|
9838
9975
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9839
|
-
return
|
|
9976
|
+
return await strategy.validateBreakeven(symbol, currentPrice);
|
|
9840
9977
|
};
|
|
9841
9978
|
/**
|
|
9842
9979
|
* Delegates to ClientStrategy.breakeven() with current execution context.
|
|
@@ -9913,9 +10050,9 @@ class StrategyConnectionService {
|
|
|
9913
10050
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
9914
10051
|
* @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
|
|
9915
10052
|
*/
|
|
9916
|
-
this.validateAverageBuy = (backtest, symbol, currentPrice, context) => {
|
|
10053
|
+
this.validateAverageBuy = async (backtest, symbol, currentPrice, context) => {
|
|
9917
10054
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9918
|
-
return
|
|
10055
|
+
return await strategy.validateAverageBuy(symbol, currentPrice);
|
|
9919
10056
|
};
|
|
9920
10057
|
/**
|
|
9921
10058
|
* Adds a new DCA entry to the active pending signal.
|
|
@@ -9936,7 +10073,8 @@ class StrategyConnectionService {
|
|
|
9936
10073
|
backtest,
|
|
9937
10074
|
});
|
|
9938
10075
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
9939
|
-
|
|
10076
|
+
const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
|
|
10077
|
+
return await strategy.averageBuy(symbol, currentPrice, backtest, timestamp, cost);
|
|
9940
10078
|
};
|
|
9941
10079
|
}
|
|
9942
10080
|
}
|
|
@@ -10677,7 +10815,7 @@ class ClientRisk {
|
|
|
10677
10815
|
* @param backtest - Whether running in backtest mode
|
|
10678
10816
|
* @returns Unique string key for memoization
|
|
10679
10817
|
*/
|
|
10680
|
-
const CREATE_KEY_FN$
|
|
10818
|
+
const CREATE_KEY_FN$n = (riskName, exchangeName, frameName, backtest) => {
|
|
10681
10819
|
const parts = [riskName, exchangeName];
|
|
10682
10820
|
if (frameName)
|
|
10683
10821
|
parts.push(frameName);
|
|
@@ -10776,7 +10914,7 @@ class RiskConnectionService {
|
|
|
10776
10914
|
* @param backtest - True if backtest mode, false if live mode
|
|
10777
10915
|
* @returns Configured ClientRisk instance
|
|
10778
10916
|
*/
|
|
10779
|
-
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
10917
|
+
this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
|
|
10780
10918
|
const schema = this.riskSchemaService.get(riskName);
|
|
10781
10919
|
return new ClientRisk({
|
|
10782
10920
|
...schema,
|
|
@@ -10844,7 +10982,7 @@ class RiskConnectionService {
|
|
|
10844
10982
|
payload,
|
|
10845
10983
|
});
|
|
10846
10984
|
if (payload) {
|
|
10847
|
-
const key = CREATE_KEY_FN$
|
|
10985
|
+
const key = CREATE_KEY_FN$n(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
10848
10986
|
this.getRisk.clear(key);
|
|
10849
10987
|
}
|
|
10850
10988
|
else {
|
|
@@ -10864,7 +11002,6 @@ const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
|
|
|
10864
11002
|
const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
|
|
10865
11003
|
const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
|
|
10866
11004
|
const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
|
|
10867
|
-
const METHOD_NAME_SIGNAL_SYNC = "ActionBase.signalSync";
|
|
10868
11005
|
const METHOD_NAME_DISPOSE = "ActionBase.dispose";
|
|
10869
11006
|
const DEFAULT_SOURCE = "default";
|
|
10870
11007
|
/**
|
|
@@ -11276,6 +11413,11 @@ class ActionProxy {
|
|
|
11276
11413
|
*/
|
|
11277
11414
|
async signalSync(event) {
|
|
11278
11415
|
if (this._target.signalSync) {
|
|
11416
|
+
console.error("Action::signalSync is unwanted cause exchange integration should be implemented in Broker.useBrokerAdapter as an infrastructure domain layer");
|
|
11417
|
+
console.error("If you need to implement custom logic on signal open/close, please use signal(), signalBacktest(), signalLive()");
|
|
11418
|
+
console.error("If Action::signalSync throws the exchange will not execute the order!");
|
|
11419
|
+
console.error("");
|
|
11420
|
+
console.error("You have been warned!");
|
|
11279
11421
|
await this._target.signalSync(event);
|
|
11280
11422
|
}
|
|
11281
11423
|
}
|
|
@@ -11738,19 +11880,6 @@ class ActionBase {
|
|
|
11738
11880
|
source,
|
|
11739
11881
|
});
|
|
11740
11882
|
}
|
|
11741
|
-
/**
|
|
11742
|
-
* Gate for position open/close via limit order. Default allows all.
|
|
11743
|
-
* Throw to reject — framework retries next tick.
|
|
11744
|
-
*
|
|
11745
|
-
* NOTE: Exceptions are NOT swallowed — they propagate to CREATE_SYNC_FN.
|
|
11746
|
-
*
|
|
11747
|
-
* @param event - Sync event with action "signal-open" or "signal-close"
|
|
11748
|
-
*/
|
|
11749
|
-
signalSync(_event, source = DEFAULT_SOURCE) {
|
|
11750
|
-
bt.loggerService.info(METHOD_NAME_SIGNAL_SYNC, {
|
|
11751
|
-
source,
|
|
11752
|
-
});
|
|
11753
|
-
}
|
|
11754
11883
|
/**
|
|
11755
11884
|
* Cleans up resources and subscriptions when action handler is disposed.
|
|
11756
11885
|
*
|
|
@@ -12320,7 +12449,7 @@ class ClientAction {
|
|
|
12320
12449
|
* @param backtest - Whether running in backtest mode
|
|
12321
12450
|
* @returns Unique string key for memoization
|
|
12322
12451
|
*/
|
|
12323
|
-
const CREATE_KEY_FN$
|
|
12452
|
+
const CREATE_KEY_FN$m = (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12324
12453
|
const parts = [actionName, strategyName, exchangeName];
|
|
12325
12454
|
if (frameName)
|
|
12326
12455
|
parts.push(frameName);
|
|
@@ -12371,7 +12500,7 @@ class ActionConnectionService {
|
|
|
12371
12500
|
* @param backtest - True if backtest mode, false if live mode
|
|
12372
12501
|
* @returns Configured ClientAction instance
|
|
12373
12502
|
*/
|
|
12374
|
-
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
12503
|
+
this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
|
|
12375
12504
|
const schema = this.actionSchemaService.get(actionName);
|
|
12376
12505
|
return new ClientAction({
|
|
12377
12506
|
...schema,
|
|
@@ -12581,7 +12710,7 @@ class ActionConnectionService {
|
|
|
12581
12710
|
await Promise.all(actions.map(async (action) => await action.dispose()));
|
|
12582
12711
|
return;
|
|
12583
12712
|
}
|
|
12584
|
-
const key = CREATE_KEY_FN$
|
|
12713
|
+
const key = CREATE_KEY_FN$m(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
12585
12714
|
if (!this.getAction.has(key)) {
|
|
12586
12715
|
return;
|
|
12587
12716
|
}
|
|
@@ -12599,7 +12728,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
|
|
|
12599
12728
|
* @param exchangeName - Exchange name
|
|
12600
12729
|
* @returns Unique string key for memoization
|
|
12601
12730
|
*/
|
|
12602
|
-
const CREATE_KEY_FN$
|
|
12731
|
+
const CREATE_KEY_FN$l = (exchangeName) => {
|
|
12603
12732
|
return exchangeName;
|
|
12604
12733
|
};
|
|
12605
12734
|
/**
|
|
@@ -12623,7 +12752,7 @@ class ExchangeCoreService {
|
|
|
12623
12752
|
* @param exchangeName - Name of the exchange to validate
|
|
12624
12753
|
* @returns Promise that resolves when validation is complete
|
|
12625
12754
|
*/
|
|
12626
|
-
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$
|
|
12755
|
+
this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$l(exchangeName), async (exchangeName) => {
|
|
12627
12756
|
this.loggerService.log(METHOD_NAME_VALIDATE$2, {
|
|
12628
12757
|
exchangeName,
|
|
12629
12758
|
});
|
|
@@ -12875,7 +13004,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
|
|
|
12875
13004
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
12876
13005
|
* @returns Unique string key for memoization
|
|
12877
13006
|
*/
|
|
12878
|
-
const CREATE_KEY_FN$
|
|
13007
|
+
const CREATE_KEY_FN$k = (context) => {
|
|
12879
13008
|
const parts = [context.strategyName, context.exchangeName];
|
|
12880
13009
|
if (context.frameName)
|
|
12881
13010
|
parts.push(context.frameName);
|
|
@@ -12907,7 +13036,7 @@ class StrategyCoreService {
|
|
|
12907
13036
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
12908
13037
|
* @returns Promise that resolves when validation is complete
|
|
12909
13038
|
*/
|
|
12910
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
13039
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
|
|
12911
13040
|
this.loggerService.log(METHOD_NAME_VALIDATE$1, {
|
|
12912
13041
|
context,
|
|
12913
13042
|
});
|
|
@@ -13742,7 +13871,7 @@ class SizingGlobalService {
|
|
|
13742
13871
|
* @param context - Context with riskName, exchangeName, frameName
|
|
13743
13872
|
* @returns Unique string key for memoization
|
|
13744
13873
|
*/
|
|
13745
|
-
const CREATE_KEY_FN$
|
|
13874
|
+
const CREATE_KEY_FN$j = (context) => {
|
|
13746
13875
|
const parts = [context.riskName, context.exchangeName];
|
|
13747
13876
|
if (context.frameName)
|
|
13748
13877
|
parts.push(context.frameName);
|
|
@@ -13768,7 +13897,7 @@ class RiskGlobalService {
|
|
|
13768
13897
|
* @param payload - Payload with riskName, exchangeName and frameName
|
|
13769
13898
|
* @returns Promise that resolves when validation is complete
|
|
13770
13899
|
*/
|
|
13771
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
13900
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
|
|
13772
13901
|
this.loggerService.log("riskGlobalService validate", {
|
|
13773
13902
|
context,
|
|
13774
13903
|
});
|
|
@@ -13846,7 +13975,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
|
|
|
13846
13975
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
13847
13976
|
* @returns Unique string key for memoization
|
|
13848
13977
|
*/
|
|
13849
|
-
const CREATE_KEY_FN$
|
|
13978
|
+
const CREATE_KEY_FN$i = (context) => {
|
|
13850
13979
|
const parts = [context.strategyName, context.exchangeName];
|
|
13851
13980
|
if (context.frameName)
|
|
13852
13981
|
parts.push(context.frameName);
|
|
@@ -13890,7 +14019,7 @@ class ActionCoreService {
|
|
|
13890
14019
|
* @param context - Strategy execution context with strategyName, exchangeName and frameName
|
|
13891
14020
|
* @returns Promise that resolves when all validations complete
|
|
13892
14021
|
*/
|
|
13893
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
14022
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
|
|
13894
14023
|
this.loggerService.log(METHOD_NAME_VALIDATE, {
|
|
13895
14024
|
context,
|
|
13896
14025
|
});
|
|
@@ -18425,7 +18554,7 @@ const Markdown = new MarkdownAdapter();
|
|
|
18425
18554
|
* @param backtest - Whether running in backtest mode
|
|
18426
18555
|
* @returns Unique string key for memoization
|
|
18427
18556
|
*/
|
|
18428
|
-
const CREATE_KEY_FN$
|
|
18557
|
+
const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
18429
18558
|
const parts = [symbol, strategyName, exchangeName];
|
|
18430
18559
|
if (frameName)
|
|
18431
18560
|
parts.push(frameName);
|
|
@@ -18664,7 +18793,7 @@ class BacktestMarkdownService {
|
|
|
18664
18793
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
18665
18794
|
* Each combination gets its own isolated storage instance.
|
|
18666
18795
|
*/
|
|
18667
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
18796
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
|
|
18668
18797
|
/**
|
|
18669
18798
|
* Processes tick events and accumulates closed signals.
|
|
18670
18799
|
* Should be called from IStrategyCallbacks.onTick.
|
|
@@ -18821,7 +18950,7 @@ class BacktestMarkdownService {
|
|
|
18821
18950
|
payload,
|
|
18822
18951
|
});
|
|
18823
18952
|
if (payload) {
|
|
18824
|
-
const key = CREATE_KEY_FN$
|
|
18953
|
+
const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
18825
18954
|
this.getStorage.clear(key);
|
|
18826
18955
|
}
|
|
18827
18956
|
else {
|
|
@@ -18883,7 +19012,7 @@ class BacktestMarkdownService {
|
|
|
18883
19012
|
* @param backtest - Whether running in backtest mode
|
|
18884
19013
|
* @returns Unique string key for memoization
|
|
18885
19014
|
*/
|
|
18886
|
-
const CREATE_KEY_FN$
|
|
19015
|
+
const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
18887
19016
|
const parts = [symbol, strategyName, exchangeName];
|
|
18888
19017
|
if (frameName)
|
|
18889
19018
|
parts.push(frameName);
|
|
@@ -19366,7 +19495,7 @@ class LiveMarkdownService {
|
|
|
19366
19495
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19367
19496
|
* Each combination gets its own isolated storage instance.
|
|
19368
19497
|
*/
|
|
19369
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
19498
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
|
|
19370
19499
|
/**
|
|
19371
19500
|
* Subscribes to live signal emitter to receive tick events.
|
|
19372
19501
|
* Protected against multiple subscriptions.
|
|
@@ -19584,7 +19713,7 @@ class LiveMarkdownService {
|
|
|
19584
19713
|
payload,
|
|
19585
19714
|
});
|
|
19586
19715
|
if (payload) {
|
|
19587
|
-
const key = CREATE_KEY_FN$
|
|
19716
|
+
const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
19588
19717
|
this.getStorage.clear(key);
|
|
19589
19718
|
}
|
|
19590
19719
|
else {
|
|
@@ -19604,7 +19733,7 @@ class LiveMarkdownService {
|
|
|
19604
19733
|
* @param backtest - Whether running in backtest mode
|
|
19605
19734
|
* @returns Unique string key for memoization
|
|
19606
19735
|
*/
|
|
19607
|
-
const CREATE_KEY_FN$
|
|
19736
|
+
const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
19608
19737
|
const parts = [symbol, strategyName, exchangeName];
|
|
19609
19738
|
if (frameName)
|
|
19610
19739
|
parts.push(frameName);
|
|
@@ -19895,7 +20024,7 @@ class ScheduleMarkdownService {
|
|
|
19895
20024
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
19896
20025
|
* Each combination gets its own isolated storage instance.
|
|
19897
20026
|
*/
|
|
19898
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20027
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
|
|
19899
20028
|
/**
|
|
19900
20029
|
* Subscribes to signal emitter to receive scheduled signal events.
|
|
19901
20030
|
* Protected against multiple subscriptions.
|
|
@@ -20098,7 +20227,7 @@ class ScheduleMarkdownService {
|
|
|
20098
20227
|
payload,
|
|
20099
20228
|
});
|
|
20100
20229
|
if (payload) {
|
|
20101
|
-
const key = CREATE_KEY_FN$
|
|
20230
|
+
const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20102
20231
|
this.getStorage.clear(key);
|
|
20103
20232
|
}
|
|
20104
20233
|
else {
|
|
@@ -20118,7 +20247,7 @@ class ScheduleMarkdownService {
|
|
|
20118
20247
|
* @param backtest - Whether running in backtest mode
|
|
20119
20248
|
* @returns Unique string key for memoization
|
|
20120
20249
|
*/
|
|
20121
|
-
const CREATE_KEY_FN$
|
|
20250
|
+
const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
20122
20251
|
const parts = [symbol, strategyName, exchangeName];
|
|
20123
20252
|
if (frameName)
|
|
20124
20253
|
parts.push(frameName);
|
|
@@ -20366,7 +20495,7 @@ class PerformanceMarkdownService {
|
|
|
20366
20495
|
* Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
20367
20496
|
* Each combination gets its own isolated storage instance.
|
|
20368
20497
|
*/
|
|
20369
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
20498
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
|
|
20370
20499
|
/**
|
|
20371
20500
|
* Subscribes to performance emitter to receive performance events.
|
|
20372
20501
|
* Protected against multiple subscriptions.
|
|
@@ -20533,7 +20662,7 @@ class PerformanceMarkdownService {
|
|
|
20533
20662
|
payload,
|
|
20534
20663
|
});
|
|
20535
20664
|
if (payload) {
|
|
20536
|
-
const key = CREATE_KEY_FN$
|
|
20665
|
+
const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
20537
20666
|
this.getStorage.clear(key);
|
|
20538
20667
|
}
|
|
20539
20668
|
else {
|
|
@@ -21003,7 +21132,7 @@ class WalkerMarkdownService {
|
|
|
21003
21132
|
* @param backtest - Whether running in backtest mode
|
|
21004
21133
|
* @returns Unique string key for memoization
|
|
21005
21134
|
*/
|
|
21006
|
-
const CREATE_KEY_FN$
|
|
21135
|
+
const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
|
|
21007
21136
|
const parts = [exchangeName];
|
|
21008
21137
|
if (frameName)
|
|
21009
21138
|
parts.push(frameName);
|
|
@@ -21370,7 +21499,7 @@ class HeatMarkdownService {
|
|
|
21370
21499
|
* Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
|
|
21371
21500
|
* Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
|
|
21372
21501
|
*/
|
|
21373
|
-
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
21502
|
+
this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
|
|
21374
21503
|
/**
|
|
21375
21504
|
* Subscribes to signal emitter to receive tick events.
|
|
21376
21505
|
* Protected against multiple subscriptions.
|
|
@@ -21565,7 +21694,7 @@ class HeatMarkdownService {
|
|
|
21565
21694
|
payload,
|
|
21566
21695
|
});
|
|
21567
21696
|
if (payload) {
|
|
21568
|
-
const key = CREATE_KEY_FN$
|
|
21697
|
+
const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
|
|
21569
21698
|
this.getStorage.clear(key);
|
|
21570
21699
|
}
|
|
21571
21700
|
else {
|
|
@@ -22596,7 +22725,7 @@ class ClientPartial {
|
|
|
22596
22725
|
* @param backtest - Whether running in backtest mode
|
|
22597
22726
|
* @returns Unique string key for memoization
|
|
22598
22727
|
*/
|
|
22599
|
-
const CREATE_KEY_FN$
|
|
22728
|
+
const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
22600
22729
|
/**
|
|
22601
22730
|
* Creates a callback function for emitting profit events to partialProfitSubject.
|
|
22602
22731
|
*
|
|
@@ -22718,7 +22847,7 @@ class PartialConnectionService {
|
|
|
22718
22847
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
22719
22848
|
* Value: ClientPartial instance with logger and event emitters
|
|
22720
22849
|
*/
|
|
22721
|
-
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
22850
|
+
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
|
|
22722
22851
|
return new ClientPartial({
|
|
22723
22852
|
signalId,
|
|
22724
22853
|
logger: this.loggerService,
|
|
@@ -22808,7 +22937,7 @@ class PartialConnectionService {
|
|
|
22808
22937
|
const partial = this.getPartial(data.id, backtest);
|
|
22809
22938
|
await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
22810
22939
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
22811
|
-
const key = CREATE_KEY_FN$
|
|
22940
|
+
const key = CREATE_KEY_FN$c(data.id, backtest);
|
|
22812
22941
|
this.getPartial.clear(key);
|
|
22813
22942
|
};
|
|
22814
22943
|
}
|
|
@@ -22824,7 +22953,7 @@ class PartialConnectionService {
|
|
|
22824
22953
|
* @param backtest - Whether running in backtest mode
|
|
22825
22954
|
* @returns Unique string key for memoization
|
|
22826
22955
|
*/
|
|
22827
|
-
const CREATE_KEY_FN$
|
|
22956
|
+
const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
22828
22957
|
const parts = [symbol, strategyName, exchangeName];
|
|
22829
22958
|
if (frameName)
|
|
22830
22959
|
parts.push(frameName);
|
|
@@ -23049,7 +23178,7 @@ class PartialMarkdownService {
|
|
|
23049
23178
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
23050
23179
|
* Each combination gets its own isolated storage instance.
|
|
23051
23180
|
*/
|
|
23052
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
23181
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
|
|
23053
23182
|
/**
|
|
23054
23183
|
* Subscribes to partial profit/loss signal emitters to receive events.
|
|
23055
23184
|
* Protected against multiple subscriptions.
|
|
@@ -23259,7 +23388,7 @@ class PartialMarkdownService {
|
|
|
23259
23388
|
payload,
|
|
23260
23389
|
});
|
|
23261
23390
|
if (payload) {
|
|
23262
|
-
const key = CREATE_KEY_FN$
|
|
23391
|
+
const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
23263
23392
|
this.getStorage.clear(key);
|
|
23264
23393
|
}
|
|
23265
23394
|
else {
|
|
@@ -23275,7 +23404,7 @@ class PartialMarkdownService {
|
|
|
23275
23404
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
23276
23405
|
* @returns Unique string key for memoization
|
|
23277
23406
|
*/
|
|
23278
|
-
const CREATE_KEY_FN$
|
|
23407
|
+
const CREATE_KEY_FN$a = (context) => {
|
|
23279
23408
|
const parts = [context.strategyName, context.exchangeName];
|
|
23280
23409
|
if (context.frameName)
|
|
23281
23410
|
parts.push(context.frameName);
|
|
@@ -23349,7 +23478,7 @@ class PartialGlobalService {
|
|
|
23349
23478
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
23350
23479
|
* @param methodName - Name of the calling method for error tracking
|
|
23351
23480
|
*/
|
|
23352
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
23481
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
|
|
23353
23482
|
this.loggerService.log("partialGlobalService validate", {
|
|
23354
23483
|
context,
|
|
23355
23484
|
methodName,
|
|
@@ -23804,7 +23933,7 @@ class ClientBreakeven {
|
|
|
23804
23933
|
* @param backtest - Whether running in backtest mode
|
|
23805
23934
|
* @returns Unique string key for memoization
|
|
23806
23935
|
*/
|
|
23807
|
-
const CREATE_KEY_FN$
|
|
23936
|
+
const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
|
|
23808
23937
|
/**
|
|
23809
23938
|
* Creates a callback function for emitting breakeven events to breakevenSubject.
|
|
23810
23939
|
*
|
|
@@ -23890,7 +24019,7 @@ class BreakevenConnectionService {
|
|
|
23890
24019
|
* Key format: "signalId:backtest" or "signalId:live"
|
|
23891
24020
|
* Value: ClientBreakeven instance with logger and event emitter
|
|
23892
24021
|
*/
|
|
23893
|
-
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$
|
|
24022
|
+
this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
|
|
23894
24023
|
return new ClientBreakeven({
|
|
23895
24024
|
signalId,
|
|
23896
24025
|
logger: this.loggerService,
|
|
@@ -23951,7 +24080,7 @@ class BreakevenConnectionService {
|
|
|
23951
24080
|
const breakeven = this.getBreakeven(data.id, backtest);
|
|
23952
24081
|
await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
|
|
23953
24082
|
await breakeven.clear(symbol, data, priceClose, backtest);
|
|
23954
|
-
const key = CREATE_KEY_FN$
|
|
24083
|
+
const key = CREATE_KEY_FN$9(data.id, backtest);
|
|
23955
24084
|
this.getBreakeven.clear(key);
|
|
23956
24085
|
};
|
|
23957
24086
|
}
|
|
@@ -23967,7 +24096,7 @@ class BreakevenConnectionService {
|
|
|
23967
24096
|
* @param backtest - Whether running in backtest mode
|
|
23968
24097
|
* @returns Unique string key for memoization
|
|
23969
24098
|
*/
|
|
23970
|
-
const CREATE_KEY_FN$
|
|
24099
|
+
const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
23971
24100
|
const parts = [symbol, strategyName, exchangeName];
|
|
23972
24101
|
if (frameName)
|
|
23973
24102
|
parts.push(frameName);
|
|
@@ -24144,7 +24273,7 @@ class BreakevenMarkdownService {
|
|
|
24144
24273
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24145
24274
|
* Each combination gets its own isolated storage instance.
|
|
24146
24275
|
*/
|
|
24147
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24276
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
|
|
24148
24277
|
/**
|
|
24149
24278
|
* Subscribes to breakeven signal emitter to receive events.
|
|
24150
24279
|
* Protected against multiple subscriptions.
|
|
@@ -24333,7 +24462,7 @@ class BreakevenMarkdownService {
|
|
|
24333
24462
|
payload,
|
|
24334
24463
|
});
|
|
24335
24464
|
if (payload) {
|
|
24336
|
-
const key = CREATE_KEY_FN$
|
|
24465
|
+
const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
24337
24466
|
this.getStorage.clear(key);
|
|
24338
24467
|
}
|
|
24339
24468
|
else {
|
|
@@ -24349,7 +24478,7 @@ class BreakevenMarkdownService {
|
|
|
24349
24478
|
* @param context - Context with strategyName, exchangeName, frameName
|
|
24350
24479
|
* @returns Unique string key for memoization
|
|
24351
24480
|
*/
|
|
24352
|
-
const CREATE_KEY_FN$
|
|
24481
|
+
const CREATE_KEY_FN$7 = (context) => {
|
|
24353
24482
|
const parts = [context.strategyName, context.exchangeName];
|
|
24354
24483
|
if (context.frameName)
|
|
24355
24484
|
parts.push(context.frameName);
|
|
@@ -24423,7 +24552,7 @@ class BreakevenGlobalService {
|
|
|
24423
24552
|
* @param context - Context with strategyName, exchangeName and frameName
|
|
24424
24553
|
* @param methodName - Name of the calling method for error tracking
|
|
24425
24554
|
*/
|
|
24426
|
-
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$
|
|
24555
|
+
this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
|
|
24427
24556
|
this.loggerService.log("breakevenGlobalService validate", {
|
|
24428
24557
|
context,
|
|
24429
24558
|
methodName,
|
|
@@ -24643,7 +24772,7 @@ class ConfigValidationService {
|
|
|
24643
24772
|
* @param backtest - Whether running in backtest mode
|
|
24644
24773
|
* @returns Unique string key for memoization
|
|
24645
24774
|
*/
|
|
24646
|
-
const CREATE_KEY_FN$
|
|
24775
|
+
const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
24647
24776
|
const parts = [symbol, strategyName, exchangeName];
|
|
24648
24777
|
if (frameName)
|
|
24649
24778
|
parts.push(frameName);
|
|
@@ -24812,7 +24941,7 @@ class RiskMarkdownService {
|
|
|
24812
24941
|
* Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
|
|
24813
24942
|
* Each combination gets its own isolated storage instance.
|
|
24814
24943
|
*/
|
|
24815
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
24944
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
|
|
24816
24945
|
/**
|
|
24817
24946
|
* Subscribes to risk rejection emitter to receive rejection events.
|
|
24818
24947
|
* Protected against multiple subscriptions.
|
|
@@ -25001,7 +25130,7 @@ class RiskMarkdownService {
|
|
|
25001
25130
|
payload,
|
|
25002
25131
|
});
|
|
25003
25132
|
if (payload) {
|
|
25004
|
-
const key = CREATE_KEY_FN$
|
|
25133
|
+
const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
25005
25134
|
this.getStorage.clear(key);
|
|
25006
25135
|
}
|
|
25007
25136
|
else {
|
|
@@ -27689,7 +27818,7 @@ class SyncReportService {
|
|
|
27689
27818
|
* @returns Colon-separated key string for memoization
|
|
27690
27819
|
* @internal
|
|
27691
27820
|
*/
|
|
27692
|
-
const CREATE_KEY_FN$
|
|
27821
|
+
const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
27693
27822
|
const parts = [symbol, strategyName, exchangeName];
|
|
27694
27823
|
if (frameName)
|
|
27695
27824
|
parts.push(frameName);
|
|
@@ -27937,7 +28066,7 @@ class StrategyMarkdownService {
|
|
|
27937
28066
|
*
|
|
27938
28067
|
* @internal
|
|
27939
28068
|
*/
|
|
27940
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
28069
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
|
|
27941
28070
|
/**
|
|
27942
28071
|
* Records a cancel-scheduled event when a scheduled signal is cancelled.
|
|
27943
28072
|
*
|
|
@@ -28505,7 +28634,7 @@ class StrategyMarkdownService {
|
|
|
28505
28634
|
this.clear = async (payload) => {
|
|
28506
28635
|
this.loggerService.log("strategyMarkdownService clear", { payload });
|
|
28507
28636
|
if (payload) {
|
|
28508
|
-
const key = CREATE_KEY_FN$
|
|
28637
|
+
const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
28509
28638
|
this.getStorage.clear(key);
|
|
28510
28639
|
}
|
|
28511
28640
|
else {
|
|
@@ -28613,7 +28742,7 @@ class StrategyMarkdownService {
|
|
|
28613
28742
|
* Creates a unique key for memoizing ReportStorage instances.
|
|
28614
28743
|
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
28615
28744
|
*/
|
|
28616
|
-
const CREATE_KEY_FN$
|
|
28745
|
+
const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
28617
28746
|
const parts = [symbol, strategyName, exchangeName];
|
|
28618
28747
|
if (frameName)
|
|
28619
28748
|
parts.push(frameName);
|
|
@@ -28745,7 +28874,7 @@ class ReportStorage {
|
|
|
28745
28874
|
class SyncMarkdownService {
|
|
28746
28875
|
constructor() {
|
|
28747
28876
|
this.loggerService = inject(TYPES.loggerService);
|
|
28748
|
-
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$
|
|
28877
|
+
this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
|
|
28749
28878
|
this.subscribe = functoolsKit.singleshot(() => {
|
|
28750
28879
|
this.loggerService.log("syncMarkdownService init");
|
|
28751
28880
|
const unsubscribe = syncSubject.subscribe(this.tick);
|
|
@@ -28820,7 +28949,7 @@ class SyncMarkdownService {
|
|
|
28820
28949
|
this.clear = async (payload) => {
|
|
28821
28950
|
this.loggerService.log("syncMarkdownService clear", { payload });
|
|
28822
28951
|
if (payload) {
|
|
28823
|
-
const key = CREATE_KEY_FN$
|
|
28952
|
+
const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
28824
28953
|
this.getStorage.clear(key);
|
|
28825
28954
|
}
|
|
28826
28955
|
else {
|
|
@@ -28830,6 +28959,275 @@ class SyncMarkdownService {
|
|
|
28830
28959
|
}
|
|
28831
28960
|
}
|
|
28832
28961
|
|
|
28962
|
+
const LISTEN_TIMEOUT$1 = 120000;
|
|
28963
|
+
/**
|
|
28964
|
+
* Creates a unique memoization key for a price stream.
|
|
28965
|
+
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
28966
|
+
*
|
|
28967
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
28968
|
+
* @param strategyName - Strategy identifier
|
|
28969
|
+
* @param exchangeName - Exchange identifier
|
|
28970
|
+
* @param frameName - Frame identifier (omitted when empty)
|
|
28971
|
+
* @param backtest - Whether running in backtest mode
|
|
28972
|
+
* @returns Unique string key for memoization
|
|
28973
|
+
*/
|
|
28974
|
+
const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
28975
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
28976
|
+
if (frameName)
|
|
28977
|
+
parts.push(frameName);
|
|
28978
|
+
parts.push(backtest ? "backtest" : "live");
|
|
28979
|
+
return parts.join(":");
|
|
28980
|
+
};
|
|
28981
|
+
/**
|
|
28982
|
+
* Service for tracking the latest market price per symbol-strategy-exchange-frame combination.
|
|
28983
|
+
*
|
|
28984
|
+
* Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
|
|
28985
|
+
* by StrategyConnectionService. Consumers can synchronously read the last known price or
|
|
28986
|
+
* await the first value if none has arrived yet.
|
|
28987
|
+
*
|
|
28988
|
+
* Primary use case: providing the current price outside of a tick execution context,
|
|
28989
|
+
* e.g., when a command is triggered between ticks.
|
|
28990
|
+
*
|
|
28991
|
+
* Features:
|
|
28992
|
+
* - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
|
|
28993
|
+
* - Falls back to ExchangeConnectionService.getAveragePrice when called inside an execution context
|
|
28994
|
+
* - Waits up to LISTEN_TIMEOUT ms for the first price if none is cached yet
|
|
28995
|
+
* - clear() disposes the BehaviorSubject for a single key or all keys
|
|
28996
|
+
*
|
|
28997
|
+
* Architecture:
|
|
28998
|
+
* - Registered as singleton in DI container
|
|
28999
|
+
* - Updated by StrategyConnectionService after each tick
|
|
29000
|
+
* - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
|
|
29001
|
+
*
|
|
29002
|
+
* @example
|
|
29003
|
+
* ```typescript
|
|
29004
|
+
* const price = await backtest.priceMetaService.getCurrentPrice("BTCUSDT", context, false);
|
|
29005
|
+
* ```
|
|
29006
|
+
*/
|
|
29007
|
+
class PriceMetaService {
|
|
29008
|
+
constructor() {
|
|
29009
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
29010
|
+
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
29011
|
+
/**
|
|
29012
|
+
* Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
|
|
29013
|
+
*
|
|
29014
|
+
* Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
|
|
29015
|
+
* Instances are cached until clear() is called.
|
|
29016
|
+
*/
|
|
29017
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$3(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
29018
|
+
/**
|
|
29019
|
+
* Returns the current market price for the given symbol and context.
|
|
29020
|
+
*
|
|
29021
|
+
* When called inside an execution context (i.e., during a signal handler or action),
|
|
29022
|
+
* delegates to ExchangeConnectionService.getAveragePrice for the live exchange price.
|
|
29023
|
+
* Otherwise, reads the last value from the cached BehaviorSubject. If no value has
|
|
29024
|
+
* been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
|
|
29025
|
+
*
|
|
29026
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29027
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29028
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29029
|
+
* @returns Current market price in quote currency
|
|
29030
|
+
* @throws When no price arrives within LISTEN_TIMEOUT ms
|
|
29031
|
+
*/
|
|
29032
|
+
this.getCurrentPrice = async (symbol, context, backtest) => {
|
|
29033
|
+
this.loggerService.log("priceMetaService getCurrentPrice", {
|
|
29034
|
+
symbol,
|
|
29035
|
+
context,
|
|
29036
|
+
backtest,
|
|
29037
|
+
});
|
|
29038
|
+
if (ExecutionContextService.hasContext() &&
|
|
29039
|
+
MethodContextService.hasContext()) {
|
|
29040
|
+
return await this.exchangeConnectionService.getAveragePrice(symbol);
|
|
29041
|
+
}
|
|
29042
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29043
|
+
if (source.data) {
|
|
29044
|
+
return source.data;
|
|
29045
|
+
}
|
|
29046
|
+
console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$3(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
29047
|
+
const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
|
|
29048
|
+
if (typeof currentPrice === "symbol") {
|
|
29049
|
+
throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$3(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
29050
|
+
}
|
|
29051
|
+
return currentPrice;
|
|
29052
|
+
};
|
|
29053
|
+
/**
|
|
29054
|
+
* Pushes a new price value into the BehaviorSubject for the given key.
|
|
29055
|
+
*
|
|
29056
|
+
* Called by StrategyConnectionService after each strategy tick to keep
|
|
29057
|
+
* the cached price up to date.
|
|
29058
|
+
*
|
|
29059
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29060
|
+
* @param currentPrice - The latest price from the tick
|
|
29061
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29062
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29063
|
+
*/
|
|
29064
|
+
this.next = async (symbol, currentPrice, context, backtest) => {
|
|
29065
|
+
this.loggerService.log("priceMetaService next", {
|
|
29066
|
+
symbol,
|
|
29067
|
+
currentPrice,
|
|
29068
|
+
context,
|
|
29069
|
+
backtest,
|
|
29070
|
+
});
|
|
29071
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29072
|
+
source.next(currentPrice);
|
|
29073
|
+
};
|
|
29074
|
+
/**
|
|
29075
|
+
* Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
|
|
29076
|
+
*
|
|
29077
|
+
* When called without arguments, clears all memoized price streams.
|
|
29078
|
+
* When called with a payload, clears only the stream for the specified key.
|
|
29079
|
+
* Should be called at strategy start (Backtest/Live/Walker) to reset state.
|
|
29080
|
+
*
|
|
29081
|
+
* @param payload - Optional key to clear a single stream; omit to clear all
|
|
29082
|
+
*/
|
|
29083
|
+
this.clear = (payload) => {
|
|
29084
|
+
this.loggerService.log("priceMetaService clear", {
|
|
29085
|
+
payload
|
|
29086
|
+
});
|
|
29087
|
+
if (!payload) {
|
|
29088
|
+
this.getSource.clear();
|
|
29089
|
+
return;
|
|
29090
|
+
}
|
|
29091
|
+
const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29092
|
+
this.getSource.clear(key);
|
|
29093
|
+
};
|
|
29094
|
+
}
|
|
29095
|
+
}
|
|
29096
|
+
|
|
29097
|
+
const LISTEN_TIMEOUT = 120000;
|
|
29098
|
+
/**
|
|
29099
|
+
* Creates a unique memoization key for a timestamp stream.
|
|
29100
|
+
* Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
|
|
29101
|
+
*
|
|
29102
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29103
|
+
* @param strategyName - Strategy identifier
|
|
29104
|
+
* @param exchangeName - Exchange identifier
|
|
29105
|
+
* @param frameName - Frame identifier (omitted when empty)
|
|
29106
|
+
* @param backtest - Whether running in backtest mode
|
|
29107
|
+
* @returns Unique string key for memoization
|
|
29108
|
+
*/
|
|
29109
|
+
const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
|
|
29110
|
+
const parts = [symbol, strategyName, exchangeName];
|
|
29111
|
+
if (frameName)
|
|
29112
|
+
parts.push(frameName);
|
|
29113
|
+
parts.push(backtest ? "backtest" : "live");
|
|
29114
|
+
return parts.join(":");
|
|
29115
|
+
};
|
|
29116
|
+
/**
|
|
29117
|
+
* Service for tracking the latest candle timestamp per symbol-strategy-exchange-frame combination.
|
|
29118
|
+
*
|
|
29119
|
+
* Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
|
|
29120
|
+
* by StrategyConnectionService. Consumers can synchronously read the last known timestamp or
|
|
29121
|
+
* await the first value if none has arrived yet.
|
|
29122
|
+
*
|
|
29123
|
+
* Primary use case: providing the current candle time outside of a tick execution context,
|
|
29124
|
+
* e.g., when a command is triggered between ticks.
|
|
29125
|
+
*
|
|
29126
|
+
* Features:
|
|
29127
|
+
* - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
|
|
29128
|
+
* - Falls back to ExecutionContextService.context.when when called inside an execution context
|
|
29129
|
+
* - Waits up to LISTEN_TIMEOUT ms for the first timestamp if none is cached yet
|
|
29130
|
+
* - clear() disposes the BehaviorSubject for a single key or all keys
|
|
29131
|
+
*
|
|
29132
|
+
* Architecture:
|
|
29133
|
+
* - Registered as singleton in DI container
|
|
29134
|
+
* - Updated by StrategyConnectionService after each tick
|
|
29135
|
+
* - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
|
|
29136
|
+
*
|
|
29137
|
+
* @example
|
|
29138
|
+
* ```typescript
|
|
29139
|
+
* const ts = await backtest.timeMetaService.getTimestamp("BTCUSDT", context, false);
|
|
29140
|
+
* ```
|
|
29141
|
+
*/
|
|
29142
|
+
class TimeMetaService {
|
|
29143
|
+
constructor() {
|
|
29144
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
29145
|
+
this.executionContextService = inject(TYPES.executionContextService);
|
|
29146
|
+
/**
|
|
29147
|
+
* Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
|
|
29148
|
+
*
|
|
29149
|
+
* Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
|
|
29150
|
+
* Instances are cached until clear() is called.
|
|
29151
|
+
*/
|
|
29152
|
+
this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
|
|
29153
|
+
/**
|
|
29154
|
+
* Returns the current candle timestamp (in milliseconds) for the given symbol and context.
|
|
29155
|
+
*
|
|
29156
|
+
* When called inside an execution context (i.e., during a signal handler or action),
|
|
29157
|
+
* reads the timestamp directly from ExecutionContextService.context.when.
|
|
29158
|
+
* Otherwise, reads the last value from the cached BehaviorSubject. If no value has
|
|
29159
|
+
* been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
|
|
29160
|
+
*
|
|
29161
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29162
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29163
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29164
|
+
* @returns Unix timestamp in milliseconds of the latest processed candle
|
|
29165
|
+
* @throws When no timestamp arrives within LISTEN_TIMEOUT ms
|
|
29166
|
+
*/
|
|
29167
|
+
this.getTimestamp = async (symbol, context, backtest) => {
|
|
29168
|
+
this.loggerService.log("timeMetaService getTimestamp", {
|
|
29169
|
+
symbol,
|
|
29170
|
+
context,
|
|
29171
|
+
backtest,
|
|
29172
|
+
});
|
|
29173
|
+
if (ExecutionContextService.hasContext()) {
|
|
29174
|
+
return this.executionContextService.context.when.getTime();
|
|
29175
|
+
}
|
|
29176
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29177
|
+
if (source.data) {
|
|
29178
|
+
return source.data;
|
|
29179
|
+
}
|
|
29180
|
+
console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$2(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
|
|
29181
|
+
const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
|
|
29182
|
+
if (typeof timestamp === "symbol") {
|
|
29183
|
+
throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$2(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
|
|
29184
|
+
}
|
|
29185
|
+
return timestamp;
|
|
29186
|
+
};
|
|
29187
|
+
/**
|
|
29188
|
+
* Pushes a new timestamp value into the BehaviorSubject for the given key.
|
|
29189
|
+
*
|
|
29190
|
+
* Called by StrategyConnectionService after each strategy tick to keep
|
|
29191
|
+
* the cached timestamp up to date.
|
|
29192
|
+
*
|
|
29193
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
29194
|
+
* @param timestamp - The createdAt timestamp from the tick (milliseconds)
|
|
29195
|
+
* @param context - Strategy, exchange, and frame identifiers
|
|
29196
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
29197
|
+
*/
|
|
29198
|
+
this.next = async (symbol, timestamp, context, backtest) => {
|
|
29199
|
+
this.loggerService.log("timeMetaService next", {
|
|
29200
|
+
symbol,
|
|
29201
|
+
timestamp,
|
|
29202
|
+
context,
|
|
29203
|
+
backtest,
|
|
29204
|
+
});
|
|
29205
|
+
const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
29206
|
+
source.next(timestamp);
|
|
29207
|
+
};
|
|
29208
|
+
/**
|
|
29209
|
+
* Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
|
|
29210
|
+
*
|
|
29211
|
+
* When called without arguments, clears all memoized timestamp streams.
|
|
29212
|
+
* When called with a payload, clears only the stream for the specified key.
|
|
29213
|
+
* Should be called at strategy start (Backtest/Live/Walker) to reset state.
|
|
29214
|
+
*
|
|
29215
|
+
* @param payload - Optional key to clear a single stream; omit to clear all
|
|
29216
|
+
*/
|
|
29217
|
+
this.clear = (payload) => {
|
|
29218
|
+
this.loggerService.log("timeMetaService clear", {
|
|
29219
|
+
payload,
|
|
29220
|
+
});
|
|
29221
|
+
if (!payload) {
|
|
29222
|
+
this.getSource.clear();
|
|
29223
|
+
return;
|
|
29224
|
+
}
|
|
29225
|
+
const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
|
|
29226
|
+
this.getSource.clear(key);
|
|
29227
|
+
};
|
|
29228
|
+
}
|
|
29229
|
+
}
|
|
29230
|
+
|
|
28833
29231
|
{
|
|
28834
29232
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
28835
29233
|
}
|
|
@@ -28862,6 +29260,10 @@ class SyncMarkdownService {
|
|
|
28862
29260
|
provide(TYPES.actionCoreService, () => new ActionCoreService());
|
|
28863
29261
|
provide(TYPES.frameCoreService, () => new FrameCoreService());
|
|
28864
29262
|
}
|
|
29263
|
+
{
|
|
29264
|
+
provide(TYPES.priceMetaService, () => new PriceMetaService());
|
|
29265
|
+
provide(TYPES.timeMetaService, () => new TimeMetaService());
|
|
29266
|
+
}
|
|
28865
29267
|
{
|
|
28866
29268
|
provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
|
|
28867
29269
|
provide(TYPES.riskGlobalService, () => new RiskGlobalService());
|
|
@@ -28953,6 +29355,10 @@ const coreServices = {
|
|
|
28953
29355
|
actionCoreService: inject(TYPES.actionCoreService),
|
|
28954
29356
|
frameCoreService: inject(TYPES.frameCoreService),
|
|
28955
29357
|
};
|
|
29358
|
+
const metaServices = {
|
|
29359
|
+
timeMetaService: inject(TYPES.timeMetaService),
|
|
29360
|
+
priceMetaService: inject(TYPES.priceMetaService),
|
|
29361
|
+
};
|
|
28956
29362
|
const globalServices = {
|
|
28957
29363
|
sizingGlobalService: inject(TYPES.sizingGlobalService),
|
|
28958
29364
|
riskGlobalService: inject(TYPES.riskGlobalService),
|
|
@@ -29017,6 +29423,7 @@ const backtest = {
|
|
|
29017
29423
|
...connectionServices,
|
|
29018
29424
|
...schemaServices,
|
|
29019
29425
|
...coreServices,
|
|
29426
|
+
...metaServices,
|
|
29020
29427
|
...globalServices,
|
|
29021
29428
|
...commandServices,
|
|
29022
29429
|
...logicPrivateServices,
|
|
@@ -31781,6 +32188,11 @@ BrokerBase = functoolsKit.makeExtendable(BrokerBase);
|
|
|
31781
32188
|
*/
|
|
31782
32189
|
const Broker = new BrokerAdapter();
|
|
31783
32190
|
|
|
32191
|
+
const POSITION_OVERLAP_LADDER_DEFAULT = {
|
|
32192
|
+
upperPercent: 1.5,
|
|
32193
|
+
lowerPercent: 1.5,
|
|
32194
|
+
};
|
|
32195
|
+
|
|
31784
32196
|
const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
|
|
31785
32197
|
const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
|
|
31786
32198
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
|
|
@@ -31806,6 +32218,8 @@ const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
|
|
|
31806
32218
|
const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
|
|
31807
32219
|
const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
|
|
31808
32220
|
const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
|
|
32221
|
+
const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
|
|
32222
|
+
const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
|
|
31809
32223
|
/**
|
|
31810
32224
|
* Cancels the scheduled signal without stopping the strategy.
|
|
31811
32225
|
*
|
|
@@ -32082,6 +32496,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
|
|
|
32082
32496
|
percentShift,
|
|
32083
32497
|
currentPrice,
|
|
32084
32498
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
32499
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32085
32500
|
position: signal.position,
|
|
32086
32501
|
context: { exchangeName, frameName, strategyName },
|
|
32087
32502
|
backtest: isBacktest,
|
|
@@ -32161,6 +32576,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
|
|
|
32161
32576
|
percentShift,
|
|
32162
32577
|
currentPrice,
|
|
32163
32578
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
32579
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32164
32580
|
position: signal.position,
|
|
32165
32581
|
context: { exchangeName, frameName, strategyName },
|
|
32166
32582
|
backtest: isBacktest,
|
|
@@ -32212,6 +32628,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
|
|
|
32212
32628
|
currentPrice,
|
|
32213
32629
|
newStopLossPrice,
|
|
32214
32630
|
position: signal.position,
|
|
32631
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32215
32632
|
context: { exchangeName, frameName, strategyName },
|
|
32216
32633
|
backtest: isBacktest,
|
|
32217
32634
|
});
|
|
@@ -32261,6 +32678,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
|
|
|
32261
32678
|
percentShift,
|
|
32262
32679
|
currentPrice,
|
|
32263
32680
|
newTakeProfitPrice,
|
|
32681
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
32264
32682
|
position: signal.position,
|
|
32265
32683
|
context: { exchangeName, frameName, strategyName },
|
|
32266
32684
|
backtest: isBacktest,
|
|
@@ -32587,6 +33005,31 @@ async function getBreakeven(symbol, currentPrice) {
|
|
|
32587
33005
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32588
33006
|
return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
|
|
32589
33007
|
}
|
|
33008
|
+
/**
|
|
33009
|
+
* Returns the effective (DCA-weighted) entry price for the current pending signal.
|
|
33010
|
+
*
|
|
33011
|
+
* Uses cost-weighted harmonic mean: Σcost / Σ(cost/price).
|
|
33012
|
+
* When partial closes exist, the price is computed iteratively using
|
|
33013
|
+
* costBasisAtClose snapshots from each partial, then blended with any
|
|
33014
|
+
* DCA entries added after the last partial.
|
|
33015
|
+
* With no DCA entries, equals the original priceOpen.
|
|
33016
|
+
*
|
|
33017
|
+
* Returns null if no pending signal exists.
|
|
33018
|
+
*
|
|
33019
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33020
|
+
*
|
|
33021
|
+
* @param symbol - Trading pair symbol
|
|
33022
|
+
* @returns Promise resolving to effective entry price or null
|
|
33023
|
+
*
|
|
33024
|
+
* @example
|
|
33025
|
+
* ```typescript
|
|
33026
|
+
* import { getPositionAveragePrice } from "backtest-kit";
|
|
33027
|
+
*
|
|
33028
|
+
* const avgPrice = await getPositionAveragePrice("BTCUSDT");
|
|
33029
|
+
* // No DCA: avgPrice === priceOpen
|
|
33030
|
+
* // After DCA at lower price: avgPrice < priceOpen
|
|
33031
|
+
* ```
|
|
33032
|
+
*/
|
|
32590
33033
|
async function getPositionAveragePrice(symbol) {
|
|
32591
33034
|
bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
|
|
32592
33035
|
symbol,
|
|
@@ -32601,6 +33044,28 @@ async function getPositionAveragePrice(symbol) {
|
|
|
32601
33044
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32602
33045
|
return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32603
33046
|
}
|
|
33047
|
+
/**
|
|
33048
|
+
* Returns the number of DCA entries made for the current pending signal.
|
|
33049
|
+
*
|
|
33050
|
+
* 1 = original entry only (no DCA).
|
|
33051
|
+
* Increases by 1 with each successful commitAverageBuy().
|
|
33052
|
+
*
|
|
33053
|
+
* Returns null if no pending signal exists.
|
|
33054
|
+
*
|
|
33055
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33056
|
+
*
|
|
33057
|
+
* @param symbol - Trading pair symbol
|
|
33058
|
+
* @returns Promise resolving to entry count or null
|
|
33059
|
+
*
|
|
33060
|
+
* @example
|
|
33061
|
+
* ```typescript
|
|
33062
|
+
* import { getPositionInvestedCount } from "backtest-kit";
|
|
33063
|
+
*
|
|
33064
|
+
* const count = await getPositionInvestedCount("BTCUSDT");
|
|
33065
|
+
* // No DCA: count === 1
|
|
33066
|
+
* // After one DCA: count === 2
|
|
33067
|
+
* ```
|
|
33068
|
+
*/
|
|
32604
33069
|
async function getPositionInvestedCount(symbol) {
|
|
32605
33070
|
bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
|
|
32606
33071
|
symbol,
|
|
@@ -32615,6 +33080,28 @@ async function getPositionInvestedCount(symbol) {
|
|
|
32615
33080
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32616
33081
|
return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32617
33082
|
}
|
|
33083
|
+
/**
|
|
33084
|
+
* Returns the total invested cost basis in dollars for the current pending signal.
|
|
33085
|
+
*
|
|
33086
|
+
* Equal to the sum of all _entry costs (Σ entry.cost).
|
|
33087
|
+
* Each entry cost is set at the time of commitAverageBuy (defaults to CC_POSITION_ENTRY_COST).
|
|
33088
|
+
*
|
|
33089
|
+
* Returns null if no pending signal exists.
|
|
33090
|
+
*
|
|
33091
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33092
|
+
*
|
|
33093
|
+
* @param symbol - Trading pair symbol
|
|
33094
|
+
* @returns Promise resolving to total invested cost in dollars or null
|
|
33095
|
+
*
|
|
33096
|
+
* @example
|
|
33097
|
+
* ```typescript
|
|
33098
|
+
* import { getPositionInvestedCost } from "backtest-kit";
|
|
33099
|
+
*
|
|
33100
|
+
* const cost = await getPositionInvestedCost("BTCUSDT");
|
|
33101
|
+
* // No DCA, default cost: cost === 100
|
|
33102
|
+
* // After one DCA with default cost: cost === 200
|
|
33103
|
+
* ```
|
|
33104
|
+
*/
|
|
32618
33105
|
async function getPositionInvestedCost(symbol) {
|
|
32619
33106
|
bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
|
|
32620
33107
|
symbol,
|
|
@@ -32629,6 +33116,29 @@ async function getPositionInvestedCost(symbol) {
|
|
|
32629
33116
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32630
33117
|
return await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32631
33118
|
}
|
|
33119
|
+
/**
|
|
33120
|
+
* Returns the unrealized PNL percentage for the current pending signal at current market price.
|
|
33121
|
+
*
|
|
33122
|
+
* Accounts for partial closes, DCA entries, slippage and fees
|
|
33123
|
+
* (delegates to toProfitLossDto).
|
|
33124
|
+
*
|
|
33125
|
+
* Returns null if no pending signal exists.
|
|
33126
|
+
*
|
|
33127
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33128
|
+
* Automatically fetches current price via getAveragePrice.
|
|
33129
|
+
*
|
|
33130
|
+
* @param symbol - Trading pair symbol
|
|
33131
|
+
* @returns Promise resolving to PNL percentage or null
|
|
33132
|
+
*
|
|
33133
|
+
* @example
|
|
33134
|
+
* ```typescript
|
|
33135
|
+
* import { getPositionPnlPercent } from "backtest-kit";
|
|
33136
|
+
*
|
|
33137
|
+
* const pnlPct = await getPositionPnlPercent("BTCUSDT");
|
|
33138
|
+
* // LONG at 100, current=105: pnlPct ≈ 5
|
|
33139
|
+
* // LONG at 100, current=95: pnlPct ≈ -5
|
|
33140
|
+
* ```
|
|
33141
|
+
*/
|
|
32632
33142
|
async function getPositionPnlPercent(symbol) {
|
|
32633
33143
|
bt.loggerService.info(GET_POSITION_PNL_PERCENT_METHOD_NAME, { symbol });
|
|
32634
33144
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32778,6 +33288,29 @@ async function commitPartialLossCost(symbol, dollarAmount) {
|
|
|
32778
33288
|
});
|
|
32779
33289
|
return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
|
|
32780
33290
|
}
|
|
33291
|
+
/**
|
|
33292
|
+
* Returns the unrealized PNL in dollars for the current pending signal at current market price.
|
|
33293
|
+
*
|
|
33294
|
+
* Calculated as: pnlPercentage / 100 × totalInvestedCost.
|
|
33295
|
+
* Accounts for partial closes, DCA entries, slippage and fees.
|
|
33296
|
+
*
|
|
33297
|
+
* Returns null if no pending signal exists.
|
|
33298
|
+
*
|
|
33299
|
+
* Automatically detects backtest/live mode from execution context.
|
|
33300
|
+
* Automatically fetches current price via getAveragePrice.
|
|
33301
|
+
*
|
|
33302
|
+
* @param symbol - Trading pair symbol
|
|
33303
|
+
* @returns Promise resolving to PNL in dollars or null
|
|
33304
|
+
*
|
|
33305
|
+
* @example
|
|
33306
|
+
* ```typescript
|
|
33307
|
+
* import { getPositionPnlCost } from "backtest-kit";
|
|
33308
|
+
*
|
|
33309
|
+
* const pnlCost = await getPositionPnlCost("BTCUSDT");
|
|
33310
|
+
* // LONG at 100, invested $100, current=105: pnlCost ≈ 5
|
|
33311
|
+
* // LONG at 100, invested $200 (DCA), current=95: pnlCost ≈ -10
|
|
33312
|
+
* ```
|
|
33313
|
+
*/
|
|
32781
33314
|
async function getPositionPnlCost(symbol) {
|
|
32782
33315
|
bt.loggerService.info(GET_POSITION_PNL_COST_METHOD_NAME, { symbol });
|
|
32783
33316
|
if (!ExecutionContextService.hasContext()) {
|
|
@@ -32864,6 +33397,104 @@ async function getPositionPartials(symbol) {
|
|
|
32864
33397
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
32865
33398
|
return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
32866
33399
|
}
|
|
33400
|
+
/**
|
|
33401
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
33402
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
33403
|
+
*
|
|
33404
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
33405
|
+
* where step = level * percent / 100.
|
|
33406
|
+
* Returns false if no pending signal exists.
|
|
33407
|
+
*
|
|
33408
|
+
* @param symbol - Trading pair symbol
|
|
33409
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
33410
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
33411
|
+
* @returns Promise<boolean> - true if price overlaps an existing entry level (DCA not recommended)
|
|
33412
|
+
*
|
|
33413
|
+
* @example
|
|
33414
|
+
* ```typescript
|
|
33415
|
+
* import { getPositionEntryOverlap } from "backtest-kit";
|
|
33416
|
+
*
|
|
33417
|
+
* // LONG with levels [43000, 42000], check if 42100 is too close to 42000
|
|
33418
|
+
* const overlap = await getPositionEntryOverlap("BTCUSDT", 42100, { upperPercent: 5, lowerPercent: 5 });
|
|
33419
|
+
* // overlap = true (42100 is within 5% of 42000 = [39900, 44100])
|
|
33420
|
+
* if (!overlap) {
|
|
33421
|
+
* await commitAverageBuy("BTCUSDT");
|
|
33422
|
+
* }
|
|
33423
|
+
* ```
|
|
33424
|
+
*/
|
|
33425
|
+
async function getPositionEntryOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
|
|
33426
|
+
bt.loggerService.info(GET_POSITION_ENTRY_OVERLAP_METHOD_NAME, {
|
|
33427
|
+
symbol,
|
|
33428
|
+
currentPrice,
|
|
33429
|
+
ladder,
|
|
33430
|
+
});
|
|
33431
|
+
if (!ExecutionContextService.hasContext()) {
|
|
33432
|
+
throw new Error("getPositionEntryOverlap requires an execution context");
|
|
33433
|
+
}
|
|
33434
|
+
if (!MethodContextService.hasContext()) {
|
|
33435
|
+
throw new Error("getPositionEntryOverlap requires a method context");
|
|
33436
|
+
}
|
|
33437
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33438
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33439
|
+
const levels = await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33440
|
+
if (!levels) {
|
|
33441
|
+
return false;
|
|
33442
|
+
}
|
|
33443
|
+
return levels.some((level) => {
|
|
33444
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
33445
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
33446
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
33447
|
+
});
|
|
33448
|
+
}
|
|
33449
|
+
/**
|
|
33450
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
33451
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
33452
|
+
*
|
|
33453
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
33454
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
33455
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
33456
|
+
*
|
|
33457
|
+
* @param symbol - Trading pair symbol
|
|
33458
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
33459
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
33460
|
+
* @returns Promise<boolean> - true if price overlaps an existing partial price (partial not recommended)
|
|
33461
|
+
*
|
|
33462
|
+
* @example
|
|
33463
|
+
* ```typescript
|
|
33464
|
+
* import { getPositionPartialOverlap } from "backtest-kit";
|
|
33465
|
+
*
|
|
33466
|
+
* // Partials at [45000], check if 45100 is too close
|
|
33467
|
+
* const overlap = await getPositionPartialOverlap("BTCUSDT", 45100, { upperPercent: 1.5, lowerPercent: 1.5 });
|
|
33468
|
+
* // overlap = true (45100 is within 1.5% of 45000)
|
|
33469
|
+
* if (!overlap) {
|
|
33470
|
+
* await commitPartialProfit("BTCUSDT", 50);
|
|
33471
|
+
* }
|
|
33472
|
+
* ```
|
|
33473
|
+
*/
|
|
33474
|
+
async function getPositionPartialOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
|
|
33475
|
+
bt.loggerService.info(GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME, {
|
|
33476
|
+
symbol,
|
|
33477
|
+
currentPrice,
|
|
33478
|
+
ladder,
|
|
33479
|
+
});
|
|
33480
|
+
if (!ExecutionContextService.hasContext()) {
|
|
33481
|
+
throw new Error("getPositionPartialOverlap requires an execution context");
|
|
33482
|
+
}
|
|
33483
|
+
if (!MethodContextService.hasContext()) {
|
|
33484
|
+
throw new Error("getPositionPartialOverlap requires a method context");
|
|
33485
|
+
}
|
|
33486
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
33487
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
33488
|
+
const partials = await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
|
|
33489
|
+
if (!partials) {
|
|
33490
|
+
return false;
|
|
33491
|
+
}
|
|
33492
|
+
return partials.some((partial) => {
|
|
33493
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
33494
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
33495
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
33496
|
+
});
|
|
33497
|
+
}
|
|
32867
33498
|
|
|
32868
33499
|
const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
|
|
32869
33500
|
/**
|
|
@@ -34090,6 +34721,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
|
|
|
34090
34721
|
const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
|
|
34091
34722
|
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
|
|
34092
34723
|
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
|
|
34724
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
|
|
34725
|
+
const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
|
|
34093
34726
|
const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
|
|
34094
34727
|
const BACKTEST_METHOD_NAME_CANCEL_SCHEDULED = "Backtest.commitCancelScheduled";
|
|
34095
34728
|
const BACKTEST_METHOD_NAME_CLOSE_PENDING = "Backtest.commitClosePending";
|
|
@@ -34277,6 +34910,20 @@ class BacktestInstance {
|
|
|
34277
34910
|
frameName: context.frameName,
|
|
34278
34911
|
backtest: true,
|
|
34279
34912
|
});
|
|
34913
|
+
bt.timeMetaService.clear({
|
|
34914
|
+
symbol,
|
|
34915
|
+
strategyName: context.strategyName,
|
|
34916
|
+
exchangeName: context.exchangeName,
|
|
34917
|
+
frameName: context.frameName,
|
|
34918
|
+
backtest: true,
|
|
34919
|
+
});
|
|
34920
|
+
bt.priceMetaService.clear({
|
|
34921
|
+
symbol,
|
|
34922
|
+
strategyName: context.strategyName,
|
|
34923
|
+
exchangeName: context.exchangeName,
|
|
34924
|
+
frameName: context.frameName,
|
|
34925
|
+
backtest: true,
|
|
34926
|
+
});
|
|
34280
34927
|
}
|
|
34281
34928
|
{
|
|
34282
34929
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
@@ -34880,6 +35527,90 @@ class BacktestUtils {
|
|
|
34880
35527
|
}
|
|
34881
35528
|
return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
|
|
34882
35529
|
};
|
|
35530
|
+
/**
|
|
35531
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
35532
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
35533
|
+
*
|
|
35534
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
35535
|
+
* where step = level * percent / 100.
|
|
35536
|
+
* Returns false if no pending signal exists.
|
|
35537
|
+
*
|
|
35538
|
+
* @param symbol - Trading pair symbol
|
|
35539
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
35540
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35541
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
35542
|
+
* @returns true if price overlaps an existing entry level (DCA not recommended)
|
|
35543
|
+
*/
|
|
35544
|
+
this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
35545
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
|
|
35546
|
+
symbol,
|
|
35547
|
+
currentPrice,
|
|
35548
|
+
context,
|
|
35549
|
+
ladder,
|
|
35550
|
+
});
|
|
35551
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35552
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35553
|
+
{
|
|
35554
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35555
|
+
riskName &&
|
|
35556
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
35557
|
+
riskList &&
|
|
35558
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
35559
|
+
actions &&
|
|
35560
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
35561
|
+
}
|
|
35562
|
+
const levels = await bt.strategyCoreService.getPositionLevels(true, symbol, context);
|
|
35563
|
+
if (!levels) {
|
|
35564
|
+
return false;
|
|
35565
|
+
}
|
|
35566
|
+
return levels.some((level) => {
|
|
35567
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
35568
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
35569
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
35570
|
+
});
|
|
35571
|
+
};
|
|
35572
|
+
/**
|
|
35573
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
35574
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
35575
|
+
*
|
|
35576
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
35577
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
35578
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
35579
|
+
*
|
|
35580
|
+
* @param symbol - Trading pair symbol
|
|
35581
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
35582
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
35583
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
35584
|
+
* @returns true if price overlaps an existing partial price (partial not recommended)
|
|
35585
|
+
*/
|
|
35586
|
+
this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
35587
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
|
|
35588
|
+
symbol,
|
|
35589
|
+
currentPrice,
|
|
35590
|
+
context,
|
|
35591
|
+
ladder,
|
|
35592
|
+
});
|
|
35593
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35594
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35595
|
+
{
|
|
35596
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
35597
|
+
riskName &&
|
|
35598
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
35599
|
+
riskList &&
|
|
35600
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
35601
|
+
actions &&
|
|
35602
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
35603
|
+
}
|
|
35604
|
+
const partials = await bt.strategyCoreService.getPositionPartials(true, symbol, context);
|
|
35605
|
+
if (!partials) {
|
|
35606
|
+
return false;
|
|
35607
|
+
}
|
|
35608
|
+
return partials.some((partial) => {
|
|
35609
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
35610
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
35611
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
35612
|
+
});
|
|
35613
|
+
};
|
|
34883
35614
|
/**
|
|
34884
35615
|
* Stops the strategy from generating new signals.
|
|
34885
35616
|
*
|
|
@@ -35369,6 +36100,7 @@ class BacktestUtils {
|
|
|
35369
36100
|
percentShift,
|
|
35370
36101
|
currentPrice,
|
|
35371
36102
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
36103
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35372
36104
|
position: signal.position,
|
|
35373
36105
|
context,
|
|
35374
36106
|
backtest: true,
|
|
@@ -35453,6 +36185,7 @@ class BacktestUtils {
|
|
|
35453
36185
|
percentShift,
|
|
35454
36186
|
currentPrice,
|
|
35455
36187
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
36188
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35456
36189
|
position: signal.position,
|
|
35457
36190
|
context,
|
|
35458
36191
|
backtest: true,
|
|
@@ -35507,6 +36240,7 @@ class BacktestUtils {
|
|
|
35507
36240
|
currentPrice,
|
|
35508
36241
|
newStopLossPrice,
|
|
35509
36242
|
position: signal.position,
|
|
36243
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35510
36244
|
context,
|
|
35511
36245
|
backtest: true,
|
|
35512
36246
|
});
|
|
@@ -35560,6 +36294,7 @@ class BacktestUtils {
|
|
|
35560
36294
|
currentPrice,
|
|
35561
36295
|
newTakeProfitPrice,
|
|
35562
36296
|
position: signal.position,
|
|
36297
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
35563
36298
|
context,
|
|
35564
36299
|
backtest: true,
|
|
35565
36300
|
});
|
|
@@ -35903,6 +36638,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
|
|
|
35903
36638
|
const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
|
|
35904
36639
|
const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
|
|
35905
36640
|
const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
|
|
36641
|
+
const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
|
|
36642
|
+
const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
|
|
35906
36643
|
const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
|
|
35907
36644
|
const LIVE_METHOD_NAME_CANCEL_SCHEDULED = "Live.cancelScheduled";
|
|
35908
36645
|
const LIVE_METHOD_NAME_CLOSE_PENDING = "Live.closePending";
|
|
@@ -36091,6 +36828,20 @@ class LiveInstance {
|
|
|
36091
36828
|
frameName: "",
|
|
36092
36829
|
backtest: false,
|
|
36093
36830
|
});
|
|
36831
|
+
bt.timeMetaService.clear({
|
|
36832
|
+
symbol,
|
|
36833
|
+
strategyName: context.strategyName,
|
|
36834
|
+
exchangeName: context.exchangeName,
|
|
36835
|
+
frameName: "",
|
|
36836
|
+
backtest: false,
|
|
36837
|
+
});
|
|
36838
|
+
bt.priceMetaService.clear({
|
|
36839
|
+
symbol,
|
|
36840
|
+
strategyName: context.strategyName,
|
|
36841
|
+
exchangeName: context.exchangeName,
|
|
36842
|
+
frameName: "",
|
|
36843
|
+
backtest: false,
|
|
36844
|
+
});
|
|
36094
36845
|
}
|
|
36095
36846
|
{
|
|
36096
36847
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
@@ -36756,6 +37507,98 @@ class LiveUtils {
|
|
|
36756
37507
|
frameName: "",
|
|
36757
37508
|
});
|
|
36758
37509
|
};
|
|
37510
|
+
/**
|
|
37511
|
+
* Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
|
|
37512
|
+
* Use this to prevent duplicate DCA entries at the same price area.
|
|
37513
|
+
*
|
|
37514
|
+
* Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
|
|
37515
|
+
* where step = level * percent / 100.
|
|
37516
|
+
* Returns false if no pending signal exists.
|
|
37517
|
+
*
|
|
37518
|
+
* @param symbol - Trading pair symbol
|
|
37519
|
+
* @param currentPrice - Price to check against existing DCA levels
|
|
37520
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37521
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
37522
|
+
* @returns true if price overlaps an existing entry level (DCA not recommended)
|
|
37523
|
+
*/
|
|
37524
|
+
this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
37525
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
|
|
37526
|
+
symbol,
|
|
37527
|
+
currentPrice,
|
|
37528
|
+
context,
|
|
37529
|
+
ladder,
|
|
37530
|
+
});
|
|
37531
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37532
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37533
|
+
{
|
|
37534
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37535
|
+
riskName &&
|
|
37536
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
|
|
37537
|
+
riskList &&
|
|
37538
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
37539
|
+
actions &&
|
|
37540
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
|
|
37541
|
+
}
|
|
37542
|
+
const levels = await bt.strategyCoreService.getPositionLevels(false, symbol, {
|
|
37543
|
+
strategyName: context.strategyName,
|
|
37544
|
+
exchangeName: context.exchangeName,
|
|
37545
|
+
frameName: "",
|
|
37546
|
+
});
|
|
37547
|
+
if (!levels) {
|
|
37548
|
+
return false;
|
|
37549
|
+
}
|
|
37550
|
+
return levels.some((level) => {
|
|
37551
|
+
const upperStep = (level * ladder.upperPercent) / 100;
|
|
37552
|
+
const lowerStep = (level * ladder.lowerPercent) / 100;
|
|
37553
|
+
return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
|
|
37554
|
+
});
|
|
37555
|
+
};
|
|
37556
|
+
/**
|
|
37557
|
+
* Checks whether the current price falls within the tolerance zone of any existing partial close price.
|
|
37558
|
+
* Use this to prevent duplicate partial closes at the same price area.
|
|
37559
|
+
*
|
|
37560
|
+
* Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
|
|
37561
|
+
* for any partial, where step = partial.currentPrice * percent / 100.
|
|
37562
|
+
* Returns false if no pending signal exists or no partials have been executed yet.
|
|
37563
|
+
*
|
|
37564
|
+
* @param symbol - Trading pair symbol
|
|
37565
|
+
* @param currentPrice - Price to check against existing partial close prices
|
|
37566
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
37567
|
+
* @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
|
|
37568
|
+
* @returns true if price overlaps an existing partial price (partial not recommended)
|
|
37569
|
+
*/
|
|
37570
|
+
this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
|
|
37571
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
|
|
37572
|
+
symbol,
|
|
37573
|
+
currentPrice,
|
|
37574
|
+
context,
|
|
37575
|
+
ladder,
|
|
37576
|
+
});
|
|
37577
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37578
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37579
|
+
{
|
|
37580
|
+
const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
|
|
37581
|
+
riskName &&
|
|
37582
|
+
bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
|
|
37583
|
+
riskList &&
|
|
37584
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
37585
|
+
actions &&
|
|
37586
|
+
actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
|
|
37587
|
+
}
|
|
37588
|
+
const partials = await bt.strategyCoreService.getPositionPartials(false, symbol, {
|
|
37589
|
+
strategyName: context.strategyName,
|
|
37590
|
+
exchangeName: context.exchangeName,
|
|
37591
|
+
frameName: "",
|
|
37592
|
+
});
|
|
37593
|
+
if (!partials) {
|
|
37594
|
+
return false;
|
|
37595
|
+
}
|
|
37596
|
+
return partials.some((partial) => {
|
|
37597
|
+
const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
|
|
37598
|
+
const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
|
|
37599
|
+
return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
|
|
37600
|
+
});
|
|
37601
|
+
};
|
|
36759
37602
|
/**
|
|
36760
37603
|
* Stops the strategy from generating new signals.
|
|
36761
37604
|
*
|
|
@@ -37338,6 +38181,7 @@ class LiveUtils {
|
|
|
37338
38181
|
percentShift,
|
|
37339
38182
|
currentPrice,
|
|
37340
38183
|
newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
|
|
38184
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37341
38185
|
position: signal.position,
|
|
37342
38186
|
context,
|
|
37343
38187
|
backtest: false,
|
|
@@ -37437,6 +38281,7 @@ class LiveUtils {
|
|
|
37437
38281
|
percentShift,
|
|
37438
38282
|
currentPrice,
|
|
37439
38283
|
newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
|
|
38284
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37440
38285
|
position: signal.position,
|
|
37441
38286
|
context,
|
|
37442
38287
|
backtest: false,
|
|
@@ -37506,6 +38351,7 @@ class LiveUtils {
|
|
|
37506
38351
|
percentShift,
|
|
37507
38352
|
currentPrice,
|
|
37508
38353
|
newStopLossPrice,
|
|
38354
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37509
38355
|
position: signal.position,
|
|
37510
38356
|
context,
|
|
37511
38357
|
backtest: false,
|
|
@@ -37575,6 +38421,7 @@ class LiveUtils {
|
|
|
37575
38421
|
percentShift,
|
|
37576
38422
|
currentPrice,
|
|
37577
38423
|
newTakeProfitPrice,
|
|
38424
|
+
takeProfitPrice: signal.priceTakeProfit,
|
|
37578
38425
|
position: signal.position,
|
|
37579
38426
|
context,
|
|
37580
38427
|
backtest: false,
|
|
@@ -40143,6 +40990,20 @@ class WalkerInstance {
|
|
|
40143
40990
|
frameName: walkerSchema.frameName,
|
|
40144
40991
|
backtest: true,
|
|
40145
40992
|
});
|
|
40993
|
+
bt.timeMetaService.clear({
|
|
40994
|
+
symbol,
|
|
40995
|
+
strategyName,
|
|
40996
|
+
exchangeName: walkerSchema.exchangeName,
|
|
40997
|
+
frameName: walkerSchema.frameName,
|
|
40998
|
+
backtest: true,
|
|
40999
|
+
});
|
|
41000
|
+
bt.priceMetaService.clear({
|
|
41001
|
+
symbol,
|
|
41002
|
+
strategyName,
|
|
41003
|
+
exchangeName: walkerSchema.exchangeName,
|
|
41004
|
+
frameName: walkerSchema.frameName,
|
|
41005
|
+
backtest: true,
|
|
41006
|
+
});
|
|
40146
41007
|
}
|
|
40147
41008
|
{
|
|
40148
41009
|
const { riskName, riskList, actions } = bt.strategySchemaService.get(strategyName);
|
|
@@ -43110,13 +43971,15 @@ class NotificationMemoryBacktestUtils {
|
|
|
43110
43971
|
* Handles signal sync events (signal-open, signal-close).
|
|
43111
43972
|
* @param data - The signal sync contract data
|
|
43112
43973
|
*/
|
|
43113
|
-
this.handleSync = async (data) => {
|
|
43974
|
+
this.handleSync = functoolsKit.trycatch(async (data) => {
|
|
43114
43975
|
bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
|
|
43115
43976
|
signalId: data.signalId,
|
|
43116
43977
|
action: data.action,
|
|
43117
43978
|
});
|
|
43118
43979
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43119
|
-
}
|
|
43980
|
+
}, {
|
|
43981
|
+
defaultValue: null,
|
|
43982
|
+
});
|
|
43120
43983
|
/**
|
|
43121
43984
|
* Handles risk rejection event.
|
|
43122
43985
|
* @param data - The risk contract data
|
|
@@ -43225,8 +44088,10 @@ class NotificationDummyBacktestUtils {
|
|
|
43225
44088
|
/**
|
|
43226
44089
|
* No-op handler for signal sync event.
|
|
43227
44090
|
*/
|
|
43228
|
-
this.handleSync = async () => {
|
|
43229
|
-
}
|
|
44091
|
+
this.handleSync = functoolsKit.trycatch(async () => {
|
|
44092
|
+
}, {
|
|
44093
|
+
defaultValue: null,
|
|
44094
|
+
});
|
|
43230
44095
|
/**
|
|
43231
44096
|
* No-op handler for risk rejection event.
|
|
43232
44097
|
*/
|
|
@@ -43365,7 +44230,7 @@ class NotificationPersistBacktestUtils {
|
|
|
43365
44230
|
* Handles signal sync events (signal-open, signal-close).
|
|
43366
44231
|
* @param data - The signal sync contract data
|
|
43367
44232
|
*/
|
|
43368
|
-
this.handleSync = async (data) => {
|
|
44233
|
+
this.handleSync = functoolsKit.trycatch(async (data) => {
|
|
43369
44234
|
bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
|
|
43370
44235
|
signalId: data.signalId,
|
|
43371
44236
|
action: data.action,
|
|
@@ -43373,7 +44238,9 @@ class NotificationPersistBacktestUtils {
|
|
|
43373
44238
|
await this.waitForInit();
|
|
43374
44239
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43375
44240
|
await this._updateNotifications();
|
|
43376
|
-
}
|
|
44241
|
+
}, {
|
|
44242
|
+
defaultValue: null,
|
|
44243
|
+
});
|
|
43377
44244
|
/**
|
|
43378
44245
|
* Handles risk rejection event.
|
|
43379
44246
|
* @param data - The risk contract data
|
|
@@ -43556,13 +44423,15 @@ class NotificationMemoryLiveUtils {
|
|
|
43556
44423
|
* Handles signal sync events (signal-open, signal-close).
|
|
43557
44424
|
* @param data - The signal sync contract data
|
|
43558
44425
|
*/
|
|
43559
|
-
this.handleSync = async (data) => {
|
|
44426
|
+
this.handleSync = functoolsKit.trycatch(async (data) => {
|
|
43560
44427
|
bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SYNC, {
|
|
43561
44428
|
signalId: data.signalId,
|
|
43562
44429
|
action: data.action,
|
|
43563
44430
|
});
|
|
43564
44431
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43565
|
-
}
|
|
44432
|
+
}, {
|
|
44433
|
+
defaultValue: null,
|
|
44434
|
+
});
|
|
43566
44435
|
/**
|
|
43567
44436
|
* Handles risk rejection event.
|
|
43568
44437
|
* @param data - The risk contract data
|
|
@@ -43671,8 +44540,10 @@ class NotificationDummyLiveUtils {
|
|
|
43671
44540
|
/**
|
|
43672
44541
|
* No-op handler for signal sync event.
|
|
43673
44542
|
*/
|
|
43674
|
-
this.handleSync = async () => {
|
|
43675
|
-
}
|
|
44543
|
+
this.handleSync = functoolsKit.trycatch(async () => {
|
|
44544
|
+
}, {
|
|
44545
|
+
defaultValue: null,
|
|
44546
|
+
});
|
|
43676
44547
|
/**
|
|
43677
44548
|
* No-op handler for risk rejection event.
|
|
43678
44549
|
*/
|
|
@@ -43812,7 +44683,7 @@ class NotificationPersistLiveUtils {
|
|
|
43812
44683
|
* Handles signal sync events (signal-open, signal-close).
|
|
43813
44684
|
* @param data - The signal sync contract data
|
|
43814
44685
|
*/
|
|
43815
|
-
this.handleSync = async (data) => {
|
|
44686
|
+
this.handleSync = functoolsKit.trycatch(async (data) => {
|
|
43816
44687
|
bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SYNC, {
|
|
43817
44688
|
signalId: data.signalId,
|
|
43818
44689
|
action: data.action,
|
|
@@ -43820,7 +44691,9 @@ class NotificationPersistLiveUtils {
|
|
|
43820
44691
|
await this.waitForInit();
|
|
43821
44692
|
this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
|
|
43822
44693
|
await this._updateNotifications();
|
|
43823
|
-
}
|
|
44694
|
+
}, {
|
|
44695
|
+
defaultValue: null,
|
|
44696
|
+
});
|
|
43824
44697
|
/**
|
|
43825
44698
|
* Handles risk rejection event.
|
|
43826
44699
|
* @param data - The risk contract data
|
|
@@ -43982,9 +44855,11 @@ class NotificationBacktestAdapter {
|
|
|
43982
44855
|
* Proxies call to the underlying notification adapter.
|
|
43983
44856
|
* @param data - The signal sync contract data
|
|
43984
44857
|
*/
|
|
43985
|
-
this.handleSync = async (data) => {
|
|
44858
|
+
this.handleSync = functoolsKit.trycatch(async (data) => {
|
|
43986
44859
|
return await this._notificationBacktestUtils.handleSync(data);
|
|
43987
|
-
}
|
|
44860
|
+
}, {
|
|
44861
|
+
defaultValue: null,
|
|
44862
|
+
});
|
|
43988
44863
|
/**
|
|
43989
44864
|
* Handles risk rejection event.
|
|
43990
44865
|
* Proxies call to the underlying notification adapter.
|
|
@@ -44126,9 +45001,11 @@ class NotificationLiveAdapter {
|
|
|
44126
45001
|
* Proxies call to the underlying notification adapter.
|
|
44127
45002
|
* @param data - The signal sync contract data
|
|
44128
45003
|
*/
|
|
44129
|
-
this.handleSync = async (data) => {
|
|
45004
|
+
this.handleSync = functoolsKit.trycatch(async (data) => {
|
|
44130
45005
|
return await this._notificationLiveUtils.handleSync(data);
|
|
44131
|
-
}
|
|
45006
|
+
}, {
|
|
45007
|
+
defaultValue: null,
|
|
45008
|
+
});
|
|
44132
45009
|
/**
|
|
44133
45010
|
* Handles risk rejection event.
|
|
44134
45011
|
* Proxies call to the underlying notification adapter.
|
|
@@ -45575,9 +46452,11 @@ exports.getNextCandles = getNextCandles;
|
|
|
45575
46452
|
exports.getOrderBook = getOrderBook;
|
|
45576
46453
|
exports.getPendingSignal = getPendingSignal;
|
|
45577
46454
|
exports.getPositionAveragePrice = getPositionAveragePrice;
|
|
46455
|
+
exports.getPositionEntryOverlap = getPositionEntryOverlap;
|
|
45578
46456
|
exports.getPositionInvestedCost = getPositionInvestedCost;
|
|
45579
46457
|
exports.getPositionInvestedCount = getPositionInvestedCount;
|
|
45580
46458
|
exports.getPositionLevels = getPositionLevels;
|
|
46459
|
+
exports.getPositionPartialOverlap = getPositionPartialOverlap;
|
|
45581
46460
|
exports.getPositionPartials = getPositionPartials;
|
|
45582
46461
|
exports.getPositionPnlCost = getPositionPnlCost;
|
|
45583
46462
|
exports.getPositionPnlPercent = getPositionPnlPercent;
|