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.
Files changed (4) hide show
  1. package/build/index.cjs +1025 -146
  2. package/build/index.mjs +1025 -148
  3. package/package.json +1 -1
  4. package/types.d.ts +624 -30
package/build/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createActivator } from 'di-kit';
2
2
  import { scoped } from 'di-scoped';
3
- import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, errorData, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, Source, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, singlerun } from 'functools-kit';
3
+ import { Subject, makeExtendable, singleshot, getErrorMessage, memoize, not, errorData, trycatch, retry, queued, sleep, randomString, str, isObject, ToolRegistry, typo, and, Source, resolveDocuments, timeout, TIMEOUT_SYMBOL as TIMEOUT_SYMBOL$1, compose, BehaviorSubject, waitForNext, singlerun } from 'functools-kit';
4
4
  import * as fs from 'fs/promises';
5
5
  import fs__default, { stat, opendir, readFile } from 'fs/promises';
6
6
  import path, { join, dirname } from 'path';
@@ -69,6 +69,10 @@ const coreServices$1 = {
69
69
  actionCoreService: Symbol('actionCoreService'),
70
70
  frameCoreService: Symbol('frameCoreService'),
71
71
  };
72
+ const metaServices$1 = {
73
+ priceMetaService: Symbol('priceMetaService'),
74
+ timeMetaService: Symbol('timeMetaService'),
75
+ };
72
76
  const globalServices$1 = {
73
77
  sizingGlobalService: Symbol('sizingGlobalService'),
74
78
  riskGlobalService: Symbol('riskGlobalService'),
@@ -134,6 +138,7 @@ const TYPES = {
134
138
  ...connectionServices$1,
135
139
  ...schemaServices$1,
136
140
  ...coreServices$1,
141
+ ...metaServices$1,
137
142
  ...globalServices$1,
138
143
  ...commandServices$1,
139
144
  ...logicPrivateServices$1,
@@ -450,9 +455,17 @@ const GLOBAL_CONFIG = {
450
455
  * Allows to commitAverageBuy if currentPrice is not the lowest price since entry, but still lower than priceOpen.
451
456
  * This can help improve average entry price in cases where price has rebounded after entry but is still below priceOpen, without waiting for a new lower price.
452
457
  *
453
- * Default: true (DCA logic enabled everywhere, not just when antirecord is broken)
458
+ * Default: false (DCA logic enabled only when antirecord is broken)
454
459
  */
455
460
  CC_ENABLE_DCA_EVERYWHERE: false,
461
+ /**
462
+ * Enables PPPL (Partial Profit, Partial Loss) logic even if this breaks a direction of exits
463
+ * Allows to take partial profit or loss on a position even if it results in a mix of profit and loss exits
464
+ * This can help lock in profits or cut losses on part of the position without waiting for a perfect exit scenario.
465
+ *
466
+ * Default: false (PPPL logic is only applied when it does not break the direction of exits, ensuring clearer profit/loss outcomes)
467
+ */
468
+ CC_ENABLE_PPPL_EVERYWHERE: false,
456
469
  /**
457
470
  * Cost of entering a position (in USD).
458
471
  * This is used as a default value for calculating position size and risk management when cost data is not provided by the strategy
@@ -3530,19 +3543,6 @@ const beginTime = (run) => (...args) => {
3530
3543
  return fn();
3531
3544
  };
3532
3545
 
3533
- /**
3534
- * Retrieves the current timestamp for debugging purposes.
3535
- * 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.
3536
- * Can be empty (undefined) if not called from strategy async context, as it's intended for debugging and not critical for logic.
3537
- * @return {number | undefined} The current timestamp in milliseconds from the execution context, or undefined if not available.
3538
- */
3539
- const getDebugTimestamp = () => {
3540
- if (ExecutionContextService.hasContext()) {
3541
- return bt.executionContextService.context.when.getTime();
3542
- }
3543
- return undefined;
3544
- };
3545
-
3546
3546
  const INTERVAL_MINUTES$6 = {
3547
3547
  "1m": 1,
3548
3548
  "3m": 3,
@@ -4250,7 +4250,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4250
4250
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
4251
4251
  timestamp: currentTime,
4252
4252
  _isScheduled: false,
4253
- _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: currentTime }],
4253
+ _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4254
4254
  };
4255
4255
  // Валидируем сигнал перед возвратом
4256
4256
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4274,7 +4274,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4274
4274
  pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
4275
4275
  timestamp: currentTime,
4276
4276
  _isScheduled: true,
4277
- _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: currentTime }],
4277
+ _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4278
4278
  };
4279
4279
  // Валидируем сигнал перед возвратом
4280
4280
  VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
@@ -4294,7 +4294,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4294
4294
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
4295
4295
  timestamp: currentTime,
4296
4296
  _isScheduled: false,
4297
- _entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: currentTime }],
4297
+ _entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4298
4298
  };
4299
4299
  // Валидируем сигнал перед возвратом
4300
4300
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4364,7 +4364,7 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
4364
4364
  self.params.logger.debug("ClientStrategy dispose");
4365
4365
  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);
4366
4366
  };
4367
- const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4367
+ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
4368
4368
  // Initialize partial array if not present
4369
4369
  if (!signal._partial)
4370
4370
  signal._partial = [];
@@ -4393,7 +4393,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4393
4393
  entryCountAtClose,
4394
4394
  currentPrice,
4395
4395
  costBasisAtClose: remainingCostBasis,
4396
- debugTimestamp: getDebugTimestamp(),
4396
+ timestamp,
4397
4397
  });
4398
4398
  self.params.logger.info("PARTIAL_PROFIT_FN executed", {
4399
4399
  signalId: signal.id,
@@ -4403,7 +4403,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4403
4403
  });
4404
4404
  return true;
4405
4405
  };
4406
- const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
4406
+ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
4407
4407
  // Initialize partial array if not present
4408
4408
  if (!signal._partial)
4409
4409
  signal._partial = [];
@@ -4431,7 +4431,7 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
4431
4431
  currentPrice,
4432
4432
  entryCountAtClose,
4433
4433
  costBasisAtClose: remainingCostBasis,
4434
- debugTimestamp: getDebugTimestamp(),
4434
+ timestamp,
4435
4435
  });
4436
4436
  self.params.logger.warn("PARTIAL_LOSS_FN executed", {
4437
4437
  signalId: signal.id,
@@ -4828,10 +4828,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4828
4828
  });
4829
4829
  return true;
4830
4830
  };
4831
- const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
4831
+ const AVERAGE_BUY_FN = (self, signal, currentPrice, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
4832
4832
  // Ensure _entry is initialized (handles signals loaded from disk without _entry)
4833
4833
  if (!signal._entry || signal._entry.length === 0) {
4834
- signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: getDebugTimestamp() }];
4834
+ signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
4835
4835
  }
4836
4836
  if (signal.position === "long") {
4837
4837
  // LONG: new entry must beat the all-time low — strictly below every prior entry price
@@ -4861,7 +4861,7 @@ const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSI
4861
4861
  return false;
4862
4862
  }
4863
4863
  }
4864
- signal._entry.push({ price: currentPrice, cost, debugTimestamp: getDebugTimestamp() });
4864
+ signal._entry.push({ price: currentPrice, cost, timestamp });
4865
4865
  self.params.logger.info("AVERAGE_BUY_FN executed", {
4866
4866
  signalId: signal.id,
4867
4867
  position: signal.position,
@@ -6646,16 +6646,16 @@ class ClientStrategy {
6646
6646
  * // No DCA: [{ price: 43000, cost: 100 }]
6647
6647
  * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
6648
6648
  */
6649
- async getPositionEntries(symbol) {
6649
+ async getPositionEntries(symbol, timestamp) {
6650
6650
  this.params.logger.debug("ClientStrategy getPositionEntries", { symbol });
6651
6651
  if (!this._pendingSignal) {
6652
6652
  return null;
6653
6653
  }
6654
6654
  const entries = this._pendingSignal._entry;
6655
6655
  if (!entries || entries.length === 0) {
6656
- return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST }];
6656
+ return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
6657
6657
  }
6658
- return entries.map(({ price, cost }) => ({ price, cost }));
6658
+ return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
6659
6659
  }
6660
6660
  /**
6661
6661
  * Performs a single tick of strategy execution.
@@ -7428,10 +7428,12 @@ class ClientStrategy {
7428
7428
  if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
7429
7429
  return false;
7430
7430
  const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
7431
- if (this._pendingSignal.position === "long" && currentPrice <= effectivePriceOpen)
7432
- return false;
7433
- if (this._pendingSignal.position === "short" && currentPrice >= effectivePriceOpen)
7434
- return false;
7431
+ if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
7432
+ if (this._pendingSignal.position === "long" && currentPrice <= effectivePriceOpen)
7433
+ return false;
7434
+ if (this._pendingSignal.position === "short" && currentPrice >= effectivePriceOpen)
7435
+ return false;
7436
+ }
7435
7437
  const effectiveTakeProfit = this._pendingSignal._trailingPriceTakeProfit ?? this._pendingSignal.priceTakeProfit;
7436
7438
  if (this._pendingSignal.position === "long" && currentPrice >= effectiveTakeProfit)
7437
7439
  return false;
@@ -7489,7 +7491,7 @@ class ClientStrategy {
7489
7491
  * // success3 = false (skipped, would exceed 100%)
7490
7492
  * ```
7491
7493
  */
7492
- async partialProfit(symbol, percentToClose, currentPrice, backtest) {
7494
+ async partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp) {
7493
7495
  this.params.logger.debug("ClientStrategy partialProfit", {
7494
7496
  symbol,
7495
7497
  percentToClose,
@@ -7515,7 +7517,7 @@ class ClientStrategy {
7515
7517
  throw new Error(`ClientStrategy partialProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
7516
7518
  }
7517
7519
  // Validation: currentPrice must be moving toward TP (profit direction)
7518
- {
7520
+ if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
7519
7521
  const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
7520
7522
  if (this._pendingSignal.position === "long") {
7521
7523
  // For LONG: currentPrice must be higher than effectivePriceOpen (moving toward TP)
@@ -7553,7 +7555,7 @@ class ClientStrategy {
7553
7555
  return false;
7554
7556
  }
7555
7557
  // Execute partial close logic
7556
- const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice);
7558
+ const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
7557
7559
  // If partial was not executed (exceeded 100%), return false without persistence
7558
7560
  if (!wasExecuted) {
7559
7561
  return false;
@@ -7609,10 +7611,12 @@ class ClientStrategy {
7609
7611
  if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0)
7610
7612
  return false;
7611
7613
  const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
7612
- if (this._pendingSignal.position === "long" && currentPrice >= effectivePriceOpen)
7613
- return false;
7614
- if (this._pendingSignal.position === "short" && currentPrice <= effectivePriceOpen)
7615
- return false;
7614
+ if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
7615
+ if (this._pendingSignal.position === "long" && currentPrice >= effectivePriceOpen)
7616
+ return false;
7617
+ if (this._pendingSignal.position === "short" && currentPrice <= effectivePriceOpen)
7618
+ return false;
7619
+ }
7616
7620
  const effectiveStopLoss = this._pendingSignal._trailingPriceStopLoss ?? this._pendingSignal.priceStopLoss;
7617
7621
  if (this._pendingSignal.position === "long" && currentPrice <= effectiveStopLoss)
7618
7622
  return false;
@@ -7670,7 +7674,7 @@ class ClientStrategy {
7670
7674
  * // success3 = false (skipped, would exceed 100%)
7671
7675
  * ```
7672
7676
  */
7673
- async partialLoss(symbol, percentToClose, currentPrice, backtest) {
7677
+ async partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp) {
7674
7678
  this.params.logger.debug("ClientStrategy partialLoss", {
7675
7679
  symbol,
7676
7680
  percentToClose,
@@ -7696,7 +7700,7 @@ class ClientStrategy {
7696
7700
  throw new Error(`ClientStrategy partialLoss: currentPrice must be a positive finite number, got ${currentPrice}`);
7697
7701
  }
7698
7702
  // Validation: currentPrice must be moving toward SL (loss direction)
7699
- {
7703
+ if (!GLOBAL_CONFIG.CC_ENABLE_PPPL_EVERYWHERE) {
7700
7704
  const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
7701
7705
  if (this._pendingSignal.position === "long") {
7702
7706
  // For LONG: currentPrice must be lower than effectivePriceOpen (moving toward SL)
@@ -7734,7 +7738,7 @@ class ClientStrategy {
7734
7738
  return false;
7735
7739
  }
7736
7740
  // Execute partial close logic
7737
- const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice);
7741
+ const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
7738
7742
  // If partial was not executed (exceeded 100%), return false without persistence
7739
7743
  if (!wasExecuted) {
7740
7744
  return false;
@@ -8490,7 +8494,7 @@ class ClientStrategy {
8490
8494
  * @param backtest - Whether running in backtest mode
8491
8495
  * @returns Promise<boolean> - true if entry added, false if rejected by direction check
8492
8496
  */
8493
- async averageBuy(symbol, currentPrice, backtest, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
8497
+ async averageBuy(symbol, currentPrice, backtest, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
8494
8498
  this.params.logger.debug("ClientStrategy averageBuy", {
8495
8499
  symbol,
8496
8500
  currentPrice,
@@ -8505,7 +8509,7 @@ class ClientStrategy {
8505
8509
  throw new Error(`ClientStrategy averageBuy: currentPrice must be a positive finite number, got ${currentPrice}`);
8506
8510
  }
8507
8511
  // Execute averaging logic
8508
- const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, cost);
8512
+ const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, timestamp, cost);
8509
8513
  if (!result) {
8510
8514
  return false;
8511
8515
  }
@@ -8969,7 +8973,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
8969
8973
  * @param backtest - Whether running in backtest mode
8970
8974
  * @returns Unique string key for memoization
8971
8975
  */
8972
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
8976
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
8973
8977
  const parts = [symbol, strategyName, exchangeName];
8974
8978
  if (frameName)
8975
8979
  parts.push(frameName);
@@ -9146,6 +9150,8 @@ class StrategyConnectionService {
9146
9150
  this.partialConnectionService = inject(TYPES.partialConnectionService);
9147
9151
  this.breakevenConnectionService = inject(TYPES.breakevenConnectionService);
9148
9152
  this.actionCoreService = inject(TYPES.actionCoreService);
9153
+ this.timeMetaService = inject(TYPES.timeMetaService);
9154
+ this.priceMetaService = inject(TYPES.priceMetaService);
9149
9155
  /**
9150
9156
  * Retrieves memoized ClientStrategy instance for given symbol-strategy pair with exchange and frame isolation.
9151
9157
  *
@@ -9159,7 +9165,7 @@ class StrategyConnectionService {
9159
9165
  * @param backtest - Whether running in backtest mode
9160
9166
  * @returns Configured ClientStrategy instance
9161
9167
  */
9162
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9168
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9163
9169
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
9164
9170
  return new ClientStrategy({
9165
9171
  symbol,
@@ -9246,6 +9252,20 @@ class StrategyConnectionService {
9246
9252
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9247
9253
  return await strategy.getTotalCostClosed(symbol);
9248
9254
  };
9255
+ /**
9256
+ * Returns the effective (DCA-averaged) entry price for the current pending signal.
9257
+ *
9258
+ * This is the harmonic mean of all _entry prices, which is the correct
9259
+ * cost-basis price used in all PNL calculations.
9260
+ * With no DCA entries, equals the original priceOpen.
9261
+ *
9262
+ * Returns null if no pending signal exists.
9263
+ *
9264
+ * @param backtest - Whether running in backtest mode
9265
+ * @param symbol - Trading pair symbol
9266
+ * @param context - Execution context with strategyName, exchangeName, frameName
9267
+ * @returns Promise resolving to effective entry price or null
9268
+ */
9249
9269
  this.getPositionAveragePrice = async (backtest, symbol, context) => {
9250
9270
  this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
9251
9271
  symbol,
@@ -9255,6 +9275,19 @@ class StrategyConnectionService {
9255
9275
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9256
9276
  return await strategy.getPositionAveragePrice(symbol);
9257
9277
  };
9278
+ /**
9279
+ * Returns the number of DCA entries made for the current pending signal.
9280
+ *
9281
+ * 1 = original entry only (no DCA).
9282
+ * Increases by 1 with each successful commitAverageBuy().
9283
+ *
9284
+ * Returns null if no pending signal exists.
9285
+ *
9286
+ * @param backtest - Whether running in backtest mode
9287
+ * @param symbol - Trading pair symbol
9288
+ * @param context - Execution context with strategyName, exchangeName, frameName
9289
+ * @returns Promise resolving to entry count or null
9290
+ */
9258
9291
  this.getPositionInvestedCount = async (backtest, symbol, context) => {
9259
9292
  this.loggerService.log("strategyConnectionService getPositionInvestedCount", {
9260
9293
  symbol,
@@ -9264,6 +9297,19 @@ class StrategyConnectionService {
9264
9297
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9265
9298
  return await strategy.getPositionInvestedCount(symbol);
9266
9299
  };
9300
+ /**
9301
+ * Returns the total invested cost basis in dollars for the current pending signal.
9302
+ *
9303
+ * Equal to entryCount × $100 (COST_BASIS_PER_ENTRY).
9304
+ * 1 entry = $100, 2 entries = $200, etc.
9305
+ *
9306
+ * Returns null if no pending signal exists.
9307
+ *
9308
+ * @param backtest - Whether running in backtest mode
9309
+ * @param symbol - Trading pair symbol
9310
+ * @param context - Execution context with strategyName, exchangeName, frameName
9311
+ * @returns Promise resolving to total invested cost in dollars or null
9312
+ */
9267
9313
  this.getPositionInvestedCost = async (backtest, symbol, context) => {
9268
9314
  this.loggerService.log("strategyConnectionService getPositionInvestedCost", {
9269
9315
  symbol,
@@ -9273,6 +9319,20 @@ class StrategyConnectionService {
9273
9319
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9274
9320
  return await strategy.getPositionInvestedCost(symbol);
9275
9321
  };
9322
+ /**
9323
+ * Returns the unrealized PNL percentage for the current pending signal at currentPrice.
9324
+ *
9325
+ * Accounts for partial closes, DCA entries, slippage and fees
9326
+ * (delegates to toProfitLossDto).
9327
+ *
9328
+ * Returns null if no pending signal exists.
9329
+ *
9330
+ * @param backtest - Whether running in backtest mode
9331
+ * @param symbol - Trading pair symbol
9332
+ * @param currentPrice - Current market price
9333
+ * @param context - Execution context with strategyName, exchangeName, frameName
9334
+ * @returns Promise resolving to pnlPercentage or null
9335
+ */
9276
9336
  this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
9277
9337
  this.loggerService.log("strategyConnectionService getPositionPnlPercent", {
9278
9338
  symbol,
@@ -9283,6 +9343,20 @@ class StrategyConnectionService {
9283
9343
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9284
9344
  return await strategy.getPositionPnlPercent(symbol, currentPrice);
9285
9345
  };
9346
+ /**
9347
+ * Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
9348
+ *
9349
+ * Calculated as: pnlPercentage / 100 × totalInvestedCost
9350
+ * Accounts for partial closes, DCA entries, slippage and fees.
9351
+ *
9352
+ * Returns null if no pending signal exists.
9353
+ *
9354
+ * @param backtest - Whether running in backtest mode
9355
+ * @param symbol - Trading pair symbol
9356
+ * @param currentPrice - Current market price
9357
+ * @param context - Execution context with strategyName, exchangeName, frameName
9358
+ * @returns Promise resolving to pnl in dollars or null
9359
+ */
9286
9360
  this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
9287
9361
  this.loggerService.log("strategyConnectionService getPositionPnlCost", {
9288
9362
  symbol,
@@ -9293,6 +9367,27 @@ class StrategyConnectionService {
9293
9367
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9294
9368
  return await strategy.getPositionPnlCost(symbol, currentPrice);
9295
9369
  };
9370
+ /**
9371
+ * Returns the list of DCA entry prices for the current pending signal.
9372
+ *
9373
+ * The first element is always the original priceOpen (initial entry).
9374
+ * Each subsequent element is a price added by commitAverageBuy().
9375
+ *
9376
+ * Returns null if no pending signal exists.
9377
+ * Returns a single-element array [priceOpen] if no DCA entries were made.
9378
+ *
9379
+ * @param backtest - Whether running in backtest mode
9380
+ * @param symbol - Trading pair symbol
9381
+ * @param context - Execution context with strategyName, exchangeName, frameName
9382
+ * @returns Promise resolving to array of entry prices or null
9383
+ *
9384
+ * @example
9385
+ * ```typescript
9386
+ * // No DCA: [43000]
9387
+ * // One DCA: [43000, 42000]
9388
+ * // Two DCA: [43000, 42000, 41500]
9389
+ * ```
9390
+ */
9296
9391
  this.getPositionLevels = async (backtest, symbol, context) => {
9297
9392
  this.loggerService.log("strategyConnectionService getPositionLevels", {
9298
9393
  symbol,
@@ -9302,6 +9397,20 @@ class StrategyConnectionService {
9302
9397
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9303
9398
  return await strategy.getPositionLevels(symbol);
9304
9399
  };
9400
+ /**
9401
+ * Returns the list of partial closes for the current pending signal.
9402
+ *
9403
+ * Each entry records a partial profit or loss close event with its type,
9404
+ * percent closed, price at close, cost basis snapshot, and entry count at close.
9405
+ *
9406
+ * Returns null if no pending signal exists.
9407
+ * Returns an empty array if no partial closes have been executed.
9408
+ *
9409
+ * @param backtest - Whether running in backtest mode
9410
+ * @param symbol - Trading pair symbol
9411
+ * @param context - Execution context with strategyName, exchangeName, frameName
9412
+ * @returns Promise resolving to array of partial close records or null
9413
+ */
9305
9414
  this.getPositionPartials = async (backtest, symbol, context) => {
9306
9415
  this.loggerService.log("strategyConnectionService getPositionPartials", {
9307
9416
  symbol,
@@ -9311,6 +9420,27 @@ class StrategyConnectionService {
9311
9420
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9312
9421
  return await strategy.getPositionPartials(symbol);
9313
9422
  };
9423
+ /**
9424
+ * Returns the list of DCA entry prices and costs for the current pending signal.
9425
+ *
9426
+ * Each entry records the price and cost of a single position entry.
9427
+ * The first element is always the original priceOpen (initial entry).
9428
+ * Each subsequent element is an entry added by averageBuy().
9429
+ *
9430
+ * Returns null if no pending signal exists.
9431
+ * Returns a single-element array [{ price: priceOpen, cost }] if no DCA entries were made.
9432
+ *
9433
+ * @param backtest - Whether running in backtest mode
9434
+ * @param symbol - Trading pair symbol
9435
+ * @param context - Execution context with strategyName, exchangeName, frameName
9436
+ * @returns Promise resolving to array of entry records or null
9437
+ *
9438
+ * @example
9439
+ * ```typescript
9440
+ * // No DCA: [{ price: 43000, cost: 100 }]
9441
+ * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
9442
+ * ```
9443
+ */
9314
9444
  this.getPositionEntries = async (backtest, symbol, context) => {
9315
9445
  this.loggerService.log("strategyConnectionService getPositionEntries", {
9316
9446
  symbol,
@@ -9318,7 +9448,8 @@ class StrategyConnectionService {
9318
9448
  backtest,
9319
9449
  });
9320
9450
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9321
- return await strategy.getPositionEntries(symbol);
9451
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9452
+ return await strategy.getPositionEntries(symbol, timestamp);
9322
9453
  };
9323
9454
  /**
9324
9455
  * Retrieves the currently active scheduled signal for the strategy.
@@ -9420,6 +9551,10 @@ class StrategyConnectionService {
9420
9551
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9421
9552
  await strategy.waitForInit();
9422
9553
  const tick = await strategy.tick(symbol, context.strategyName);
9554
+ {
9555
+ this.priceMetaService.next(symbol, tick.currentPrice, context, backtest);
9556
+ this.timeMetaService.next(symbol, tick.createdAt, context, backtest);
9557
+ }
9423
9558
  {
9424
9559
  await CALL_SIGNAL_EMIT_FN(this, tick, context, backtest, symbol);
9425
9560
  }
@@ -9526,7 +9661,7 @@ class StrategyConnectionService {
9526
9661
  }
9527
9662
  return;
9528
9663
  }
9529
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9664
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9530
9665
  if (!this.getStrategy.has(key)) {
9531
9666
  return;
9532
9667
  }
@@ -9595,9 +9730,9 @@ class StrategyConnectionService {
9595
9730
  * @param context - Execution context with strategyName, exchangeName, frameName
9596
9731
  * @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
9597
9732
  */
9598
- this.validatePartialProfit = (backtest, symbol, percentToClose, currentPrice, context) => {
9733
+ this.validatePartialProfit = async (backtest, symbol, percentToClose, currentPrice, context) => {
9599
9734
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9600
- return Promise.resolve(strategy.validatePartialProfit(symbol, percentToClose, currentPrice));
9735
+ return await strategy.validatePartialProfit(symbol, percentToClose, currentPrice);
9601
9736
  };
9602
9737
  /**
9603
9738
  * Executes partial close at profit level (moving toward TP).
@@ -9638,7 +9773,8 @@ class StrategyConnectionService {
9638
9773
  backtest,
9639
9774
  });
9640
9775
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9641
- return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest);
9776
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9777
+ return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp);
9642
9778
  };
9643
9779
  /**
9644
9780
  * Checks whether `partialLoss` would succeed without executing it.
@@ -9651,9 +9787,9 @@ class StrategyConnectionService {
9651
9787
  * @param context - Execution context with strategyName, exchangeName, frameName
9652
9788
  * @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
9653
9789
  */
9654
- this.validatePartialLoss = (backtest, symbol, percentToClose, currentPrice, context) => {
9790
+ this.validatePartialLoss = async (backtest, symbol, percentToClose, currentPrice, context) => {
9655
9791
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9656
- return Promise.resolve(strategy.validatePartialLoss(symbol, percentToClose, currentPrice));
9792
+ return await strategy.validatePartialLoss(symbol, percentToClose, currentPrice);
9657
9793
  };
9658
9794
  /**
9659
9795
  * Executes partial close at loss level (moving toward SL).
@@ -9694,7 +9830,8 @@ class StrategyConnectionService {
9694
9830
  backtest,
9695
9831
  });
9696
9832
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9697
- return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest);
9833
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9834
+ return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp);
9698
9835
  };
9699
9836
  /**
9700
9837
  * Checks whether `trailingStop` would succeed without executing it.
@@ -9707,9 +9844,9 @@ class StrategyConnectionService {
9707
9844
  * @param context - Execution context with strategyName, exchangeName, frameName
9708
9845
  * @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
9709
9846
  */
9710
- this.validateTrailingStop = (backtest, symbol, percentShift, currentPrice, context) => {
9847
+ this.validateTrailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
9711
9848
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9712
- return Promise.resolve(strategy.validateTrailingStop(symbol, percentShift, currentPrice));
9849
+ return await strategy.validateTrailingStop(symbol, percentShift, currentPrice);
9713
9850
  };
9714
9851
  /**
9715
9852
  * Adjusts the trailing stop-loss distance for an active pending signal.
@@ -9761,9 +9898,9 @@ class StrategyConnectionService {
9761
9898
  * @param context - Execution context with strategyName, exchangeName, frameName
9762
9899
  * @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
9763
9900
  */
9764
- this.validateTrailingTake = (backtest, symbol, percentShift, currentPrice, context) => {
9901
+ this.validateTrailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
9765
9902
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9766
- return Promise.resolve(strategy.validateTrailingTake(symbol, percentShift, currentPrice));
9903
+ return await strategy.validateTrailingTake(symbol, percentShift, currentPrice);
9767
9904
  };
9768
9905
  /**
9769
9906
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -9814,9 +9951,9 @@ class StrategyConnectionService {
9814
9951
  * @param context - Execution context with strategyName, exchangeName, frameName
9815
9952
  * @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
9816
9953
  */
9817
- this.validateBreakeven = (backtest, symbol, currentPrice, context) => {
9954
+ this.validateBreakeven = async (backtest, symbol, currentPrice, context) => {
9818
9955
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9819
- return Promise.resolve(strategy.validateBreakeven(symbol, currentPrice));
9956
+ return await strategy.validateBreakeven(symbol, currentPrice);
9820
9957
  };
9821
9958
  /**
9822
9959
  * Delegates to ClientStrategy.breakeven() with current execution context.
@@ -9893,9 +10030,9 @@ class StrategyConnectionService {
9893
10030
  * @param context - Execution context with strategyName, exchangeName, frameName
9894
10031
  * @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
9895
10032
  */
9896
- this.validateAverageBuy = (backtest, symbol, currentPrice, context) => {
10033
+ this.validateAverageBuy = async (backtest, symbol, currentPrice, context) => {
9897
10034
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9898
- return Promise.resolve(strategy.validateAverageBuy(symbol, currentPrice));
10035
+ return await strategy.validateAverageBuy(symbol, currentPrice);
9899
10036
  };
9900
10037
  /**
9901
10038
  * Adds a new DCA entry to the active pending signal.
@@ -9916,7 +10053,8 @@ class StrategyConnectionService {
9916
10053
  backtest,
9917
10054
  });
9918
10055
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9919
- return await strategy.averageBuy(symbol, currentPrice, backtest, cost);
10056
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
10057
+ return await strategy.averageBuy(symbol, currentPrice, backtest, timestamp, cost);
9920
10058
  };
9921
10059
  }
9922
10060
  }
@@ -10657,7 +10795,7 @@ class ClientRisk {
10657
10795
  * @param backtest - Whether running in backtest mode
10658
10796
  * @returns Unique string key for memoization
10659
10797
  */
10660
- const CREATE_KEY_FN$l = (riskName, exchangeName, frameName, backtest) => {
10798
+ const CREATE_KEY_FN$n = (riskName, exchangeName, frameName, backtest) => {
10661
10799
  const parts = [riskName, exchangeName];
10662
10800
  if (frameName)
10663
10801
  parts.push(frameName);
@@ -10756,7 +10894,7 @@ class RiskConnectionService {
10756
10894
  * @param backtest - True if backtest mode, false if live mode
10757
10895
  * @returns Configured ClientRisk instance
10758
10896
  */
10759
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
10897
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
10760
10898
  const schema = this.riskSchemaService.get(riskName);
10761
10899
  return new ClientRisk({
10762
10900
  ...schema,
@@ -10824,7 +10962,7 @@ class RiskConnectionService {
10824
10962
  payload,
10825
10963
  });
10826
10964
  if (payload) {
10827
- const key = CREATE_KEY_FN$l(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
10965
+ const key = CREATE_KEY_FN$n(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
10828
10966
  this.getRisk.clear(key);
10829
10967
  }
10830
10968
  else {
@@ -10844,7 +10982,6 @@ const METHOD_NAME_PARTIAL_LOSS_AVAILABLE = "ActionBase.partialLossAvailable";
10844
10982
  const METHOD_NAME_PING_SCHEDULED = "ActionBase.pingScheduled";
10845
10983
  const METHOD_NAME_PING_ACTIVE = "ActionBase.pingActive";
10846
10984
  const METHOD_NAME_RISK_REJECTION = "ActionBase.riskRejection";
10847
- const METHOD_NAME_SIGNAL_SYNC = "ActionBase.signalSync";
10848
10985
  const METHOD_NAME_DISPOSE = "ActionBase.dispose";
10849
10986
  const DEFAULT_SOURCE = "default";
10850
10987
  /**
@@ -11256,6 +11393,11 @@ class ActionProxy {
11256
11393
  */
11257
11394
  async signalSync(event) {
11258
11395
  if (this._target.signalSync) {
11396
+ console.error("Action::signalSync is unwanted cause exchange integration should be implemented in Broker.useBrokerAdapter as an infrastructure domain layer");
11397
+ console.error("If you need to implement custom logic on signal open/close, please use signal(), signalBacktest(), signalLive()");
11398
+ console.error("If Action::signalSync throws the exchange will not execute the order!");
11399
+ console.error("");
11400
+ console.error("You have been warned!");
11259
11401
  await this._target.signalSync(event);
11260
11402
  }
11261
11403
  }
@@ -11718,19 +11860,6 @@ class ActionBase {
11718
11860
  source,
11719
11861
  });
11720
11862
  }
11721
- /**
11722
- * Gate for position open/close via limit order. Default allows all.
11723
- * Throw to reject — framework retries next tick.
11724
- *
11725
- * NOTE: Exceptions are NOT swallowed — they propagate to CREATE_SYNC_FN.
11726
- *
11727
- * @param event - Sync event with action "signal-open" or "signal-close"
11728
- */
11729
- signalSync(_event, source = DEFAULT_SOURCE) {
11730
- bt.loggerService.info(METHOD_NAME_SIGNAL_SYNC, {
11731
- source,
11732
- });
11733
- }
11734
11863
  /**
11735
11864
  * Cleans up resources and subscriptions when action handler is disposed.
11736
11865
  *
@@ -12300,7 +12429,7 @@ class ClientAction {
12300
12429
  * @param backtest - Whether running in backtest mode
12301
12430
  * @returns Unique string key for memoization
12302
12431
  */
12303
- const CREATE_KEY_FN$k = (actionName, strategyName, exchangeName, frameName, backtest) => {
12432
+ const CREATE_KEY_FN$m = (actionName, strategyName, exchangeName, frameName, backtest) => {
12304
12433
  const parts = [actionName, strategyName, exchangeName];
12305
12434
  if (frameName)
12306
12435
  parts.push(frameName);
@@ -12351,7 +12480,7 @@ class ActionConnectionService {
12351
12480
  * @param backtest - True if backtest mode, false if live mode
12352
12481
  * @returns Configured ClientAction instance
12353
12482
  */
12354
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
12483
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
12355
12484
  const schema = this.actionSchemaService.get(actionName);
12356
12485
  return new ClientAction({
12357
12486
  ...schema,
@@ -12561,7 +12690,7 @@ class ActionConnectionService {
12561
12690
  await Promise.all(actions.map(async (action) => await action.dispose()));
12562
12691
  return;
12563
12692
  }
12564
- const key = CREATE_KEY_FN$k(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
12693
+ const key = CREATE_KEY_FN$m(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
12565
12694
  if (!this.getAction.has(key)) {
12566
12695
  return;
12567
12696
  }
@@ -12579,7 +12708,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
12579
12708
  * @param exchangeName - Exchange name
12580
12709
  * @returns Unique string key for memoization
12581
12710
  */
12582
- const CREATE_KEY_FN$j = (exchangeName) => {
12711
+ const CREATE_KEY_FN$l = (exchangeName) => {
12583
12712
  return exchangeName;
12584
12713
  };
12585
12714
  /**
@@ -12603,7 +12732,7 @@ class ExchangeCoreService {
12603
12732
  * @param exchangeName - Name of the exchange to validate
12604
12733
  * @returns Promise that resolves when validation is complete
12605
12734
  */
12606
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$j(exchangeName), async (exchangeName) => {
12735
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$l(exchangeName), async (exchangeName) => {
12607
12736
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
12608
12737
  exchangeName,
12609
12738
  });
@@ -12855,7 +12984,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
12855
12984
  * @param context - Execution context with strategyName, exchangeName, frameName
12856
12985
  * @returns Unique string key for memoization
12857
12986
  */
12858
- const CREATE_KEY_FN$i = (context) => {
12987
+ const CREATE_KEY_FN$k = (context) => {
12859
12988
  const parts = [context.strategyName, context.exchangeName];
12860
12989
  if (context.frameName)
12861
12990
  parts.push(context.frameName);
@@ -12887,7 +13016,7 @@ class StrategyCoreService {
12887
13016
  * @param context - Execution context with strategyName, exchangeName, frameName
12888
13017
  * @returns Promise that resolves when validation is complete
12889
13018
  */
12890
- this.validate = memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
13019
+ this.validate = memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
12891
13020
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
12892
13021
  context,
12893
13022
  });
@@ -13722,7 +13851,7 @@ class SizingGlobalService {
13722
13851
  * @param context - Context with riskName, exchangeName, frameName
13723
13852
  * @returns Unique string key for memoization
13724
13853
  */
13725
- const CREATE_KEY_FN$h = (context) => {
13854
+ const CREATE_KEY_FN$j = (context) => {
13726
13855
  const parts = [context.riskName, context.exchangeName];
13727
13856
  if (context.frameName)
13728
13857
  parts.push(context.frameName);
@@ -13748,7 +13877,7 @@ class RiskGlobalService {
13748
13877
  * @param payload - Payload with riskName, exchangeName and frameName
13749
13878
  * @returns Promise that resolves when validation is complete
13750
13879
  */
13751
- this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
13880
+ this.validate = memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
13752
13881
  this.loggerService.log("riskGlobalService validate", {
13753
13882
  context,
13754
13883
  });
@@ -13826,7 +13955,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
13826
13955
  * @param context - Execution context with strategyName, exchangeName, frameName
13827
13956
  * @returns Unique string key for memoization
13828
13957
  */
13829
- const CREATE_KEY_FN$g = (context) => {
13958
+ const CREATE_KEY_FN$i = (context) => {
13830
13959
  const parts = [context.strategyName, context.exchangeName];
13831
13960
  if (context.frameName)
13832
13961
  parts.push(context.frameName);
@@ -13870,7 +13999,7 @@ class ActionCoreService {
13870
13999
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
13871
14000
  * @returns Promise that resolves when all validations complete
13872
14001
  */
13873
- this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
14002
+ this.validate = memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
13874
14003
  this.loggerService.log(METHOD_NAME_VALIDATE, {
13875
14004
  context,
13876
14005
  });
@@ -18405,7 +18534,7 @@ const Markdown = new MarkdownAdapter();
18405
18534
  * @param backtest - Whether running in backtest mode
18406
18535
  * @returns Unique string key for memoization
18407
18536
  */
18408
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
18537
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
18409
18538
  const parts = [symbol, strategyName, exchangeName];
18410
18539
  if (frameName)
18411
18540
  parts.push(frameName);
@@ -18644,7 +18773,7 @@ class BacktestMarkdownService {
18644
18773
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
18645
18774
  * Each combination gets its own isolated storage instance.
18646
18775
  */
18647
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
18776
+ this.getStorage = 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));
18648
18777
  /**
18649
18778
  * Processes tick events and accumulates closed signals.
18650
18779
  * Should be called from IStrategyCallbacks.onTick.
@@ -18801,7 +18930,7 @@ class BacktestMarkdownService {
18801
18930
  payload,
18802
18931
  });
18803
18932
  if (payload) {
18804
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
18933
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
18805
18934
  this.getStorage.clear(key);
18806
18935
  }
18807
18936
  else {
@@ -18863,7 +18992,7 @@ class BacktestMarkdownService {
18863
18992
  * @param backtest - Whether running in backtest mode
18864
18993
  * @returns Unique string key for memoization
18865
18994
  */
18866
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
18995
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
18867
18996
  const parts = [symbol, strategyName, exchangeName];
18868
18997
  if (frameName)
18869
18998
  parts.push(frameName);
@@ -19346,7 +19475,7 @@ class LiveMarkdownService {
19346
19475
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19347
19476
  * Each combination gets its own isolated storage instance.
19348
19477
  */
19349
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
19478
+ this.getStorage = 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));
19350
19479
  /**
19351
19480
  * Subscribes to live signal emitter to receive tick events.
19352
19481
  * Protected against multiple subscriptions.
@@ -19564,7 +19693,7 @@ class LiveMarkdownService {
19564
19693
  payload,
19565
19694
  });
19566
19695
  if (payload) {
19567
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19696
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19568
19697
  this.getStorage.clear(key);
19569
19698
  }
19570
19699
  else {
@@ -19584,7 +19713,7 @@ class LiveMarkdownService {
19584
19713
  * @param backtest - Whether running in backtest mode
19585
19714
  * @returns Unique string key for memoization
19586
19715
  */
19587
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
19716
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
19588
19717
  const parts = [symbol, strategyName, exchangeName];
19589
19718
  if (frameName)
19590
19719
  parts.push(frameName);
@@ -19875,7 +20004,7 @@ class ScheduleMarkdownService {
19875
20004
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19876
20005
  * Each combination gets its own isolated storage instance.
19877
20006
  */
19878
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
20007
+ this.getStorage = 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));
19879
20008
  /**
19880
20009
  * Subscribes to signal emitter to receive scheduled signal events.
19881
20010
  * Protected against multiple subscriptions.
@@ -20078,7 +20207,7 @@ class ScheduleMarkdownService {
20078
20207
  payload,
20079
20208
  });
20080
20209
  if (payload) {
20081
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20210
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20082
20211
  this.getStorage.clear(key);
20083
20212
  }
20084
20213
  else {
@@ -20098,7 +20227,7 @@ class ScheduleMarkdownService {
20098
20227
  * @param backtest - Whether running in backtest mode
20099
20228
  * @returns Unique string key for memoization
20100
20229
  */
20101
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
20230
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
20102
20231
  const parts = [symbol, strategyName, exchangeName];
20103
20232
  if (frameName)
20104
20233
  parts.push(frameName);
@@ -20346,7 +20475,7 @@ class PerformanceMarkdownService {
20346
20475
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
20347
20476
  * Each combination gets its own isolated storage instance.
20348
20477
  */
20349
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
20478
+ this.getStorage = 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));
20350
20479
  /**
20351
20480
  * Subscribes to performance emitter to receive performance events.
20352
20481
  * Protected against multiple subscriptions.
@@ -20513,7 +20642,7 @@ class PerformanceMarkdownService {
20513
20642
  payload,
20514
20643
  });
20515
20644
  if (payload) {
20516
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20645
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20517
20646
  this.getStorage.clear(key);
20518
20647
  }
20519
20648
  else {
@@ -20983,7 +21112,7 @@ class WalkerMarkdownService {
20983
21112
  * @param backtest - Whether running in backtest mode
20984
21113
  * @returns Unique string key for memoization
20985
21114
  */
20986
- const CREATE_KEY_FN$b = (exchangeName, frameName, backtest) => {
21115
+ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
20987
21116
  const parts = [exchangeName];
20988
21117
  if (frameName)
20989
21118
  parts.push(frameName);
@@ -21350,7 +21479,7 @@ class HeatMarkdownService {
21350
21479
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
21351
21480
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
21352
21481
  */
21353
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21482
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21354
21483
  /**
21355
21484
  * Subscribes to signal emitter to receive tick events.
21356
21485
  * Protected against multiple subscriptions.
@@ -21545,7 +21674,7 @@ class HeatMarkdownService {
21545
21674
  payload,
21546
21675
  });
21547
21676
  if (payload) {
21548
- const key = CREATE_KEY_FN$b(payload.exchangeName, payload.frameName, payload.backtest);
21677
+ const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
21549
21678
  this.getStorage.clear(key);
21550
21679
  }
21551
21680
  else {
@@ -22576,7 +22705,7 @@ class ClientPartial {
22576
22705
  * @param backtest - Whether running in backtest mode
22577
22706
  * @returns Unique string key for memoization
22578
22707
  */
22579
- const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22708
+ const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22580
22709
  /**
22581
22710
  * Creates a callback function for emitting profit events to partialProfitSubject.
22582
22711
  *
@@ -22698,7 +22827,7 @@ class PartialConnectionService {
22698
22827
  * Key format: "signalId:backtest" or "signalId:live"
22699
22828
  * Value: ClientPartial instance with logger and event emitters
22700
22829
  */
22701
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
22830
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
22702
22831
  return new ClientPartial({
22703
22832
  signalId,
22704
22833
  logger: this.loggerService,
@@ -22788,7 +22917,7 @@ class PartialConnectionService {
22788
22917
  const partial = this.getPartial(data.id, backtest);
22789
22918
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
22790
22919
  await partial.clear(symbol, data, priceClose, backtest);
22791
- const key = CREATE_KEY_FN$a(data.id, backtest);
22920
+ const key = CREATE_KEY_FN$c(data.id, backtest);
22792
22921
  this.getPartial.clear(key);
22793
22922
  };
22794
22923
  }
@@ -22804,7 +22933,7 @@ class PartialConnectionService {
22804
22933
  * @param backtest - Whether running in backtest mode
22805
22934
  * @returns Unique string key for memoization
22806
22935
  */
22807
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
22936
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
22808
22937
  const parts = [symbol, strategyName, exchangeName];
22809
22938
  if (frameName)
22810
22939
  parts.push(frameName);
@@ -23029,7 +23158,7 @@ class PartialMarkdownService {
23029
23158
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
23030
23159
  * Each combination gets its own isolated storage instance.
23031
23160
  */
23032
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
23161
+ this.getStorage = 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));
23033
23162
  /**
23034
23163
  * Subscribes to partial profit/loss signal emitters to receive events.
23035
23164
  * Protected against multiple subscriptions.
@@ -23239,7 +23368,7 @@ class PartialMarkdownService {
23239
23368
  payload,
23240
23369
  });
23241
23370
  if (payload) {
23242
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23371
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23243
23372
  this.getStorage.clear(key);
23244
23373
  }
23245
23374
  else {
@@ -23255,7 +23384,7 @@ class PartialMarkdownService {
23255
23384
  * @param context - Context with strategyName, exchangeName, frameName
23256
23385
  * @returns Unique string key for memoization
23257
23386
  */
23258
- const CREATE_KEY_FN$8 = (context) => {
23387
+ const CREATE_KEY_FN$a = (context) => {
23259
23388
  const parts = [context.strategyName, context.exchangeName];
23260
23389
  if (context.frameName)
23261
23390
  parts.push(context.frameName);
@@ -23329,7 +23458,7 @@ class PartialGlobalService {
23329
23458
  * @param context - Context with strategyName, exchangeName and frameName
23330
23459
  * @param methodName - Name of the calling method for error tracking
23331
23460
  */
23332
- this.validate = memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
23461
+ this.validate = memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
23333
23462
  this.loggerService.log("partialGlobalService validate", {
23334
23463
  context,
23335
23464
  methodName,
@@ -23784,7 +23913,7 @@ class ClientBreakeven {
23784
23913
  * @param backtest - Whether running in backtest mode
23785
23914
  * @returns Unique string key for memoization
23786
23915
  */
23787
- const CREATE_KEY_FN$7 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23916
+ const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23788
23917
  /**
23789
23918
  * Creates a callback function for emitting breakeven events to breakevenSubject.
23790
23919
  *
@@ -23870,7 +23999,7 @@ class BreakevenConnectionService {
23870
23999
  * Key format: "signalId:backtest" or "signalId:live"
23871
24000
  * Value: ClientBreakeven instance with logger and event emitter
23872
24001
  */
23873
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$7(signalId, backtest), (signalId, backtest) => {
24002
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
23874
24003
  return new ClientBreakeven({
23875
24004
  signalId,
23876
24005
  logger: this.loggerService,
@@ -23931,7 +24060,7 @@ class BreakevenConnectionService {
23931
24060
  const breakeven = this.getBreakeven(data.id, backtest);
23932
24061
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
23933
24062
  await breakeven.clear(symbol, data, priceClose, backtest);
23934
- const key = CREATE_KEY_FN$7(data.id, backtest);
24063
+ const key = CREATE_KEY_FN$9(data.id, backtest);
23935
24064
  this.getBreakeven.clear(key);
23936
24065
  };
23937
24066
  }
@@ -23947,7 +24076,7 @@ class BreakevenConnectionService {
23947
24076
  * @param backtest - Whether running in backtest mode
23948
24077
  * @returns Unique string key for memoization
23949
24078
  */
23950
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24079
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
23951
24080
  const parts = [symbol, strategyName, exchangeName];
23952
24081
  if (frameName)
23953
24082
  parts.push(frameName);
@@ -24124,7 +24253,7 @@ class BreakevenMarkdownService {
24124
24253
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24125
24254
  * Each combination gets its own isolated storage instance.
24126
24255
  */
24127
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
24256
+ this.getStorage = 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));
24128
24257
  /**
24129
24258
  * Subscribes to breakeven signal emitter to receive events.
24130
24259
  * Protected against multiple subscriptions.
@@ -24313,7 +24442,7 @@ class BreakevenMarkdownService {
24313
24442
  payload,
24314
24443
  });
24315
24444
  if (payload) {
24316
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24445
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24317
24446
  this.getStorage.clear(key);
24318
24447
  }
24319
24448
  else {
@@ -24329,7 +24458,7 @@ class BreakevenMarkdownService {
24329
24458
  * @param context - Context with strategyName, exchangeName, frameName
24330
24459
  * @returns Unique string key for memoization
24331
24460
  */
24332
- const CREATE_KEY_FN$5 = (context) => {
24461
+ const CREATE_KEY_FN$7 = (context) => {
24333
24462
  const parts = [context.strategyName, context.exchangeName];
24334
24463
  if (context.frameName)
24335
24464
  parts.push(context.frameName);
@@ -24403,7 +24532,7 @@ class BreakevenGlobalService {
24403
24532
  * @param context - Context with strategyName, exchangeName and frameName
24404
24533
  * @param methodName - Name of the calling method for error tracking
24405
24534
  */
24406
- this.validate = memoize(([context]) => CREATE_KEY_FN$5(context), (context, methodName) => {
24535
+ this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
24407
24536
  this.loggerService.log("breakevenGlobalService validate", {
24408
24537
  context,
24409
24538
  methodName,
@@ -24623,7 +24752,7 @@ class ConfigValidationService {
24623
24752
  * @param backtest - Whether running in backtest mode
24624
24753
  * @returns Unique string key for memoization
24625
24754
  */
24626
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24755
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24627
24756
  const parts = [symbol, strategyName, exchangeName];
24628
24757
  if (frameName)
24629
24758
  parts.push(frameName);
@@ -24792,7 +24921,7 @@ class RiskMarkdownService {
24792
24921
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24793
24922
  * Each combination gets its own isolated storage instance.
24794
24923
  */
24795
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
24924
+ this.getStorage = 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));
24796
24925
  /**
24797
24926
  * Subscribes to risk rejection emitter to receive rejection events.
24798
24927
  * Protected against multiple subscriptions.
@@ -24981,7 +25110,7 @@ class RiskMarkdownService {
24981
25110
  payload,
24982
25111
  });
24983
25112
  if (payload) {
24984
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25113
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24985
25114
  this.getStorage.clear(key);
24986
25115
  }
24987
25116
  else {
@@ -27669,7 +27798,7 @@ class SyncReportService {
27669
27798
  * @returns Colon-separated key string for memoization
27670
27799
  * @internal
27671
27800
  */
27672
- const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27801
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27673
27802
  const parts = [symbol, strategyName, exchangeName];
27674
27803
  if (frameName)
27675
27804
  parts.push(frameName);
@@ -27917,7 +28046,7 @@ class StrategyMarkdownService {
27917
28046
  *
27918
28047
  * @internal
27919
28048
  */
27920
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$3(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
28049
+ this.getStorage = 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));
27921
28050
  /**
27922
28051
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
27923
28052
  *
@@ -28485,7 +28614,7 @@ class StrategyMarkdownService {
28485
28614
  this.clear = async (payload) => {
28486
28615
  this.loggerService.log("strategyMarkdownService clear", { payload });
28487
28616
  if (payload) {
28488
- const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28617
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28489
28618
  this.getStorage.clear(key);
28490
28619
  }
28491
28620
  else {
@@ -28593,7 +28722,7 @@ class StrategyMarkdownService {
28593
28722
  * Creates a unique key for memoizing ReportStorage instances.
28594
28723
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28595
28724
  */
28596
- const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28725
+ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28597
28726
  const parts = [symbol, strategyName, exchangeName];
28598
28727
  if (frameName)
28599
28728
  parts.push(frameName);
@@ -28725,7 +28854,7 @@ class ReportStorage {
28725
28854
  class SyncMarkdownService {
28726
28855
  constructor() {
28727
28856
  this.loggerService = inject(TYPES.loggerService);
28728
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
28857
+ this.getStorage = 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));
28729
28858
  this.subscribe = singleshot(() => {
28730
28859
  this.loggerService.log("syncMarkdownService init");
28731
28860
  const unsubscribe = syncSubject.subscribe(this.tick);
@@ -28800,7 +28929,7 @@ class SyncMarkdownService {
28800
28929
  this.clear = async (payload) => {
28801
28930
  this.loggerService.log("syncMarkdownService clear", { payload });
28802
28931
  if (payload) {
28803
- const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28932
+ const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28804
28933
  this.getStorage.clear(key);
28805
28934
  }
28806
28935
  else {
@@ -28810,6 +28939,275 @@ class SyncMarkdownService {
28810
28939
  }
28811
28940
  }
28812
28941
 
28942
+ const LISTEN_TIMEOUT$1 = 120000;
28943
+ /**
28944
+ * Creates a unique memoization key for a price stream.
28945
+ * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28946
+ *
28947
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
28948
+ * @param strategyName - Strategy identifier
28949
+ * @param exchangeName - Exchange identifier
28950
+ * @param frameName - Frame identifier (omitted when empty)
28951
+ * @param backtest - Whether running in backtest mode
28952
+ * @returns Unique string key for memoization
28953
+ */
28954
+ const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28955
+ const parts = [symbol, strategyName, exchangeName];
28956
+ if (frameName)
28957
+ parts.push(frameName);
28958
+ parts.push(backtest ? "backtest" : "live");
28959
+ return parts.join(":");
28960
+ };
28961
+ /**
28962
+ * Service for tracking the latest market price per symbol-strategy-exchange-frame combination.
28963
+ *
28964
+ * Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
28965
+ * by StrategyConnectionService. Consumers can synchronously read the last known price or
28966
+ * await the first value if none has arrived yet.
28967
+ *
28968
+ * Primary use case: providing the current price outside of a tick execution context,
28969
+ * e.g., when a command is triggered between ticks.
28970
+ *
28971
+ * Features:
28972
+ * - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
28973
+ * - Falls back to ExchangeConnectionService.getAveragePrice when called inside an execution context
28974
+ * - Waits up to LISTEN_TIMEOUT ms for the first price if none is cached yet
28975
+ * - clear() disposes the BehaviorSubject for a single key or all keys
28976
+ *
28977
+ * Architecture:
28978
+ * - Registered as singleton in DI container
28979
+ * - Updated by StrategyConnectionService after each tick
28980
+ * - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
28981
+ *
28982
+ * @example
28983
+ * ```typescript
28984
+ * const price = await backtest.priceMetaService.getCurrentPrice("BTCUSDT", context, false);
28985
+ * ```
28986
+ */
28987
+ class PriceMetaService {
28988
+ constructor() {
28989
+ this.loggerService = inject(TYPES.loggerService);
28990
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
28991
+ /**
28992
+ * Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
28993
+ *
28994
+ * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
28995
+ * Instances are cached until clear() is called.
28996
+ */
28997
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$3(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
28998
+ /**
28999
+ * Returns the current market price for the given symbol and context.
29000
+ *
29001
+ * When called inside an execution context (i.e., during a signal handler or action),
29002
+ * delegates to ExchangeConnectionService.getAveragePrice for the live exchange price.
29003
+ * Otherwise, reads the last value from the cached BehaviorSubject. If no value has
29004
+ * been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
29005
+ *
29006
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29007
+ * @param context - Strategy, exchange, and frame identifiers
29008
+ * @param backtest - True if backtest mode, false if live mode
29009
+ * @returns Current market price in quote currency
29010
+ * @throws When no price arrives within LISTEN_TIMEOUT ms
29011
+ */
29012
+ this.getCurrentPrice = async (symbol, context, backtest) => {
29013
+ this.loggerService.log("priceMetaService getCurrentPrice", {
29014
+ symbol,
29015
+ context,
29016
+ backtest,
29017
+ });
29018
+ if (ExecutionContextService.hasContext() &&
29019
+ MethodContextService.hasContext()) {
29020
+ return await this.exchangeConnectionService.getAveragePrice(symbol);
29021
+ }
29022
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29023
+ if (source.data) {
29024
+ return source.data;
29025
+ }
29026
+ 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...`);
29027
+ const currentPrice = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
29028
+ if (typeof currentPrice === "symbol") {
29029
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$3(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
29030
+ }
29031
+ return currentPrice;
29032
+ };
29033
+ /**
29034
+ * Pushes a new price value into the BehaviorSubject for the given key.
29035
+ *
29036
+ * Called by StrategyConnectionService after each strategy tick to keep
29037
+ * the cached price up to date.
29038
+ *
29039
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29040
+ * @param currentPrice - The latest price from the tick
29041
+ * @param context - Strategy, exchange, and frame identifiers
29042
+ * @param backtest - True if backtest mode, false if live mode
29043
+ */
29044
+ this.next = async (symbol, currentPrice, context, backtest) => {
29045
+ this.loggerService.log("priceMetaService next", {
29046
+ symbol,
29047
+ currentPrice,
29048
+ context,
29049
+ backtest,
29050
+ });
29051
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29052
+ source.next(currentPrice);
29053
+ };
29054
+ /**
29055
+ * Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
29056
+ *
29057
+ * When called without arguments, clears all memoized price streams.
29058
+ * When called with a payload, clears only the stream for the specified key.
29059
+ * Should be called at strategy start (Backtest/Live/Walker) to reset state.
29060
+ *
29061
+ * @param payload - Optional key to clear a single stream; omit to clear all
29062
+ */
29063
+ this.clear = (payload) => {
29064
+ this.loggerService.log("priceMetaService clear", {
29065
+ payload
29066
+ });
29067
+ if (!payload) {
29068
+ this.getSource.clear();
29069
+ return;
29070
+ }
29071
+ const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29072
+ this.getSource.clear(key);
29073
+ };
29074
+ }
29075
+ }
29076
+
29077
+ const LISTEN_TIMEOUT = 120000;
29078
+ /**
29079
+ * Creates a unique memoization key for a timestamp stream.
29080
+ * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
29081
+ *
29082
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29083
+ * @param strategyName - Strategy identifier
29084
+ * @param exchangeName - Exchange identifier
29085
+ * @param frameName - Frame identifier (omitted when empty)
29086
+ * @param backtest - Whether running in backtest mode
29087
+ * @returns Unique string key for memoization
29088
+ */
29089
+ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29090
+ const parts = [symbol, strategyName, exchangeName];
29091
+ if (frameName)
29092
+ parts.push(frameName);
29093
+ parts.push(backtest ? "backtest" : "live");
29094
+ return parts.join(":");
29095
+ };
29096
+ /**
29097
+ * Service for tracking the latest candle timestamp per symbol-strategy-exchange-frame combination.
29098
+ *
29099
+ * Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
29100
+ * by StrategyConnectionService. Consumers can synchronously read the last known timestamp or
29101
+ * await the first value if none has arrived yet.
29102
+ *
29103
+ * Primary use case: providing the current candle time outside of a tick execution context,
29104
+ * e.g., when a command is triggered between ticks.
29105
+ *
29106
+ * Features:
29107
+ * - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
29108
+ * - Falls back to ExecutionContextService.context.when when called inside an execution context
29109
+ * - Waits up to LISTEN_TIMEOUT ms for the first timestamp if none is cached yet
29110
+ * - clear() disposes the BehaviorSubject for a single key or all keys
29111
+ *
29112
+ * Architecture:
29113
+ * - Registered as singleton in DI container
29114
+ * - Updated by StrategyConnectionService after each tick
29115
+ * - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
29116
+ *
29117
+ * @example
29118
+ * ```typescript
29119
+ * const ts = await backtest.timeMetaService.getTimestamp("BTCUSDT", context, false);
29120
+ * ```
29121
+ */
29122
+ class TimeMetaService {
29123
+ constructor() {
29124
+ this.loggerService = inject(TYPES.loggerService);
29125
+ this.executionContextService = inject(TYPES.executionContextService);
29126
+ /**
29127
+ * Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
29128
+ *
29129
+ * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
29130
+ * Instances are cached until clear() is called.
29131
+ */
29132
+ this.getSource = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), () => new BehaviorSubject());
29133
+ /**
29134
+ * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
29135
+ *
29136
+ * When called inside an execution context (i.e., during a signal handler or action),
29137
+ * reads the timestamp directly from ExecutionContextService.context.when.
29138
+ * Otherwise, reads the last value from the cached BehaviorSubject. If no value has
29139
+ * been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
29140
+ *
29141
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29142
+ * @param context - Strategy, exchange, and frame identifiers
29143
+ * @param backtest - True if backtest mode, false if live mode
29144
+ * @returns Unix timestamp in milliseconds of the latest processed candle
29145
+ * @throws When no timestamp arrives within LISTEN_TIMEOUT ms
29146
+ */
29147
+ this.getTimestamp = async (symbol, context, backtest) => {
29148
+ this.loggerService.log("timeMetaService getTimestamp", {
29149
+ symbol,
29150
+ context,
29151
+ backtest,
29152
+ });
29153
+ if (ExecutionContextService.hasContext()) {
29154
+ return this.executionContextService.context.when.getTime();
29155
+ }
29156
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29157
+ if (source.data) {
29158
+ return source.data;
29159
+ }
29160
+ 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...`);
29161
+ const timestamp = await waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
29162
+ if (typeof timestamp === "symbol") {
29163
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$2(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
29164
+ }
29165
+ return timestamp;
29166
+ };
29167
+ /**
29168
+ * Pushes a new timestamp value into the BehaviorSubject for the given key.
29169
+ *
29170
+ * Called by StrategyConnectionService after each strategy tick to keep
29171
+ * the cached timestamp up to date.
29172
+ *
29173
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29174
+ * @param timestamp - The createdAt timestamp from the tick (milliseconds)
29175
+ * @param context - Strategy, exchange, and frame identifiers
29176
+ * @param backtest - True if backtest mode, false if live mode
29177
+ */
29178
+ this.next = async (symbol, timestamp, context, backtest) => {
29179
+ this.loggerService.log("timeMetaService next", {
29180
+ symbol,
29181
+ timestamp,
29182
+ context,
29183
+ backtest,
29184
+ });
29185
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29186
+ source.next(timestamp);
29187
+ };
29188
+ /**
29189
+ * Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
29190
+ *
29191
+ * When called without arguments, clears all memoized timestamp streams.
29192
+ * When called with a payload, clears only the stream for the specified key.
29193
+ * Should be called at strategy start (Backtest/Live/Walker) to reset state.
29194
+ *
29195
+ * @param payload - Optional key to clear a single stream; omit to clear all
29196
+ */
29197
+ this.clear = (payload) => {
29198
+ this.loggerService.log("timeMetaService clear", {
29199
+ payload,
29200
+ });
29201
+ if (!payload) {
29202
+ this.getSource.clear();
29203
+ return;
29204
+ }
29205
+ const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29206
+ this.getSource.clear(key);
29207
+ };
29208
+ }
29209
+ }
29210
+
28813
29211
  {
28814
29212
  provide(TYPES.loggerService, () => new LoggerService());
28815
29213
  }
@@ -28842,6 +29240,10 @@ class SyncMarkdownService {
28842
29240
  provide(TYPES.actionCoreService, () => new ActionCoreService());
28843
29241
  provide(TYPES.frameCoreService, () => new FrameCoreService());
28844
29242
  }
29243
+ {
29244
+ provide(TYPES.priceMetaService, () => new PriceMetaService());
29245
+ provide(TYPES.timeMetaService, () => new TimeMetaService());
29246
+ }
28845
29247
  {
28846
29248
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
28847
29249
  provide(TYPES.riskGlobalService, () => new RiskGlobalService());
@@ -28933,6 +29335,10 @@ const coreServices = {
28933
29335
  actionCoreService: inject(TYPES.actionCoreService),
28934
29336
  frameCoreService: inject(TYPES.frameCoreService),
28935
29337
  };
29338
+ const metaServices = {
29339
+ timeMetaService: inject(TYPES.timeMetaService),
29340
+ priceMetaService: inject(TYPES.priceMetaService),
29341
+ };
28936
29342
  const globalServices = {
28937
29343
  sizingGlobalService: inject(TYPES.sizingGlobalService),
28938
29344
  riskGlobalService: inject(TYPES.riskGlobalService),
@@ -28997,6 +29403,7 @@ const backtest = {
28997
29403
  ...connectionServices,
28998
29404
  ...schemaServices,
28999
29405
  ...coreServices,
29406
+ ...metaServices,
29000
29407
  ...globalServices,
29001
29408
  ...commandServices,
29002
29409
  ...logicPrivateServices,
@@ -31761,6 +32168,11 @@ BrokerBase = makeExtendable(BrokerBase);
31761
32168
  */
31762
32169
  const Broker = new BrokerAdapter();
31763
32170
 
32171
+ const POSITION_OVERLAP_LADDER_DEFAULT = {
32172
+ upperPercent: 1.5,
32173
+ lowerPercent: 1.5,
32174
+ };
32175
+
31764
32176
  const CANCEL_SCHEDULED_METHOD_NAME = "strategy.commitCancelScheduled";
31765
32177
  const CLOSE_PENDING_METHOD_NAME = "strategy.commitClosePending";
31766
32178
  const PARTIAL_PROFIT_METHOD_NAME = "strategy.commitPartialProfit";
@@ -31786,6 +32198,8 @@ const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
31786
32198
  const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
31787
32199
  const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
31788
32200
  const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
32201
+ const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
32202
+ const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
31789
32203
  /**
31790
32204
  * Cancels the scheduled signal without stopping the strategy.
31791
32205
  *
@@ -32062,6 +32476,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
32062
32476
  percentShift,
32063
32477
  currentPrice,
32064
32478
  newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
32479
+ takeProfitPrice: signal.priceTakeProfit,
32065
32480
  position: signal.position,
32066
32481
  context: { exchangeName, frameName, strategyName },
32067
32482
  backtest: isBacktest,
@@ -32141,6 +32556,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
32141
32556
  percentShift,
32142
32557
  currentPrice,
32143
32558
  newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
32559
+ takeProfitPrice: signal.priceTakeProfit,
32144
32560
  position: signal.position,
32145
32561
  context: { exchangeName, frameName, strategyName },
32146
32562
  backtest: isBacktest,
@@ -32192,6 +32608,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
32192
32608
  currentPrice,
32193
32609
  newStopLossPrice,
32194
32610
  position: signal.position,
32611
+ takeProfitPrice: signal.priceTakeProfit,
32195
32612
  context: { exchangeName, frameName, strategyName },
32196
32613
  backtest: isBacktest,
32197
32614
  });
@@ -32241,6 +32658,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
32241
32658
  percentShift,
32242
32659
  currentPrice,
32243
32660
  newTakeProfitPrice,
32661
+ takeProfitPrice: signal.priceTakeProfit,
32244
32662
  position: signal.position,
32245
32663
  context: { exchangeName, frameName, strategyName },
32246
32664
  backtest: isBacktest,
@@ -32567,6 +32985,31 @@ async function getBreakeven(symbol, currentPrice) {
32567
32985
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
32568
32986
  return await bt.strategyCoreService.getBreakeven(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
32569
32987
  }
32988
+ /**
32989
+ * Returns the effective (DCA-weighted) entry price for the current pending signal.
32990
+ *
32991
+ * Uses cost-weighted harmonic mean: Σcost / Σ(cost/price).
32992
+ * When partial closes exist, the price is computed iteratively using
32993
+ * costBasisAtClose snapshots from each partial, then blended with any
32994
+ * DCA entries added after the last partial.
32995
+ * With no DCA entries, equals the original priceOpen.
32996
+ *
32997
+ * Returns null if no pending signal exists.
32998
+ *
32999
+ * Automatically detects backtest/live mode from execution context.
33000
+ *
33001
+ * @param symbol - Trading pair symbol
33002
+ * @returns Promise resolving to effective entry price or null
33003
+ *
33004
+ * @example
33005
+ * ```typescript
33006
+ * import { getPositionAveragePrice } from "backtest-kit";
33007
+ *
33008
+ * const avgPrice = await getPositionAveragePrice("BTCUSDT");
33009
+ * // No DCA: avgPrice === priceOpen
33010
+ * // After DCA at lower price: avgPrice < priceOpen
33011
+ * ```
33012
+ */
32570
33013
  async function getPositionAveragePrice(symbol) {
32571
33014
  bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
32572
33015
  symbol,
@@ -32581,6 +33024,28 @@ async function getPositionAveragePrice(symbol) {
32581
33024
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
32582
33025
  return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32583
33026
  }
33027
+ /**
33028
+ * Returns the number of DCA entries made for the current pending signal.
33029
+ *
33030
+ * 1 = original entry only (no DCA).
33031
+ * Increases by 1 with each successful commitAverageBuy().
33032
+ *
33033
+ * Returns null if no pending signal exists.
33034
+ *
33035
+ * Automatically detects backtest/live mode from execution context.
33036
+ *
33037
+ * @param symbol - Trading pair symbol
33038
+ * @returns Promise resolving to entry count or null
33039
+ *
33040
+ * @example
33041
+ * ```typescript
33042
+ * import { getPositionInvestedCount } from "backtest-kit";
33043
+ *
33044
+ * const count = await getPositionInvestedCount("BTCUSDT");
33045
+ * // No DCA: count === 1
33046
+ * // After one DCA: count === 2
33047
+ * ```
33048
+ */
32584
33049
  async function getPositionInvestedCount(symbol) {
32585
33050
  bt.loggerService.info(GET_POSITION_INVESTED_COUNT_METHOD_NAME, {
32586
33051
  symbol,
@@ -32595,6 +33060,28 @@ async function getPositionInvestedCount(symbol) {
32595
33060
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
32596
33061
  return await bt.strategyCoreService.getPositionInvestedCount(isBacktest, symbol, { exchangeName, frameName, strategyName });
32597
33062
  }
33063
+ /**
33064
+ * Returns the total invested cost basis in dollars for the current pending signal.
33065
+ *
33066
+ * Equal to the sum of all _entry costs (Σ entry.cost).
33067
+ * Each entry cost is set at the time of commitAverageBuy (defaults to CC_POSITION_ENTRY_COST).
33068
+ *
33069
+ * Returns null if no pending signal exists.
33070
+ *
33071
+ * Automatically detects backtest/live mode from execution context.
33072
+ *
33073
+ * @param symbol - Trading pair symbol
33074
+ * @returns Promise resolving to total invested cost in dollars or null
33075
+ *
33076
+ * @example
33077
+ * ```typescript
33078
+ * import { getPositionInvestedCost } from "backtest-kit";
33079
+ *
33080
+ * const cost = await getPositionInvestedCost("BTCUSDT");
33081
+ * // No DCA, default cost: cost === 100
33082
+ * // After one DCA with default cost: cost === 200
33083
+ * ```
33084
+ */
32598
33085
  async function getPositionInvestedCost(symbol) {
32599
33086
  bt.loggerService.info(GET_POSITION_INVESTED_COST_METHOD_NAME, {
32600
33087
  symbol,
@@ -32609,6 +33096,29 @@ async function getPositionInvestedCost(symbol) {
32609
33096
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
32610
33097
  return await bt.strategyCoreService.getPositionInvestedCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
32611
33098
  }
33099
+ /**
33100
+ * Returns the unrealized PNL percentage for the current pending signal at current market price.
33101
+ *
33102
+ * Accounts for partial closes, DCA entries, slippage and fees
33103
+ * (delegates to toProfitLossDto).
33104
+ *
33105
+ * Returns null if no pending signal exists.
33106
+ *
33107
+ * Automatically detects backtest/live mode from execution context.
33108
+ * Automatically fetches current price via getAveragePrice.
33109
+ *
33110
+ * @param symbol - Trading pair symbol
33111
+ * @returns Promise resolving to PNL percentage or null
33112
+ *
33113
+ * @example
33114
+ * ```typescript
33115
+ * import { getPositionPnlPercent } from "backtest-kit";
33116
+ *
33117
+ * const pnlPct = await getPositionPnlPercent("BTCUSDT");
33118
+ * // LONG at 100, current=105: pnlPct ≈ 5
33119
+ * // LONG at 100, current=95: pnlPct ≈ -5
33120
+ * ```
33121
+ */
32612
33122
  async function getPositionPnlPercent(symbol) {
32613
33123
  bt.loggerService.info(GET_POSITION_PNL_PERCENT_METHOD_NAME, { symbol });
32614
33124
  if (!ExecutionContextService.hasContext()) {
@@ -32758,6 +33268,29 @@ async function commitPartialLossCost(symbol, dollarAmount) {
32758
33268
  });
32759
33269
  return await bt.strategyCoreService.partialLoss(isBacktest, symbol, percentToClose, currentPrice, { exchangeName, frameName, strategyName });
32760
33270
  }
33271
+ /**
33272
+ * Returns the unrealized PNL in dollars for the current pending signal at current market price.
33273
+ *
33274
+ * Calculated as: pnlPercentage / 100 × totalInvestedCost.
33275
+ * Accounts for partial closes, DCA entries, slippage and fees.
33276
+ *
33277
+ * Returns null if no pending signal exists.
33278
+ *
33279
+ * Automatically detects backtest/live mode from execution context.
33280
+ * Automatically fetches current price via getAveragePrice.
33281
+ *
33282
+ * @param symbol - Trading pair symbol
33283
+ * @returns Promise resolving to PNL in dollars or null
33284
+ *
33285
+ * @example
33286
+ * ```typescript
33287
+ * import { getPositionPnlCost } from "backtest-kit";
33288
+ *
33289
+ * const pnlCost = await getPositionPnlCost("BTCUSDT");
33290
+ * // LONG at 100, invested $100, current=105: pnlCost ≈ 5
33291
+ * // LONG at 100, invested $200 (DCA), current=95: pnlCost ≈ -10
33292
+ * ```
33293
+ */
32761
33294
  async function getPositionPnlCost(symbol) {
32762
33295
  bt.loggerService.info(GET_POSITION_PNL_COST_METHOD_NAME, { symbol });
32763
33296
  if (!ExecutionContextService.hasContext()) {
@@ -32844,6 +33377,104 @@ async function getPositionPartials(symbol) {
32844
33377
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
32845
33378
  return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
32846
33379
  }
33380
+ /**
33381
+ * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
33382
+ * Use this to prevent duplicate DCA entries at the same price area.
33383
+ *
33384
+ * Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
33385
+ * where step = level * percent / 100.
33386
+ * Returns false if no pending signal exists.
33387
+ *
33388
+ * @param symbol - Trading pair symbol
33389
+ * @param currentPrice - Price to check against existing DCA levels
33390
+ * @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
33391
+ * @returns Promise<boolean> - true if price overlaps an existing entry level (DCA not recommended)
33392
+ *
33393
+ * @example
33394
+ * ```typescript
33395
+ * import { getPositionEntryOverlap } from "backtest-kit";
33396
+ *
33397
+ * // LONG with levels [43000, 42000], check if 42100 is too close to 42000
33398
+ * const overlap = await getPositionEntryOverlap("BTCUSDT", 42100, { upperPercent: 5, lowerPercent: 5 });
33399
+ * // overlap = true (42100 is within 5% of 42000 = [39900, 44100])
33400
+ * if (!overlap) {
33401
+ * await commitAverageBuy("BTCUSDT");
33402
+ * }
33403
+ * ```
33404
+ */
33405
+ async function getPositionEntryOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
33406
+ bt.loggerService.info(GET_POSITION_ENTRY_OVERLAP_METHOD_NAME, {
33407
+ symbol,
33408
+ currentPrice,
33409
+ ladder,
33410
+ });
33411
+ if (!ExecutionContextService.hasContext()) {
33412
+ throw new Error("getPositionEntryOverlap requires an execution context");
33413
+ }
33414
+ if (!MethodContextService.hasContext()) {
33415
+ throw new Error("getPositionEntryOverlap requires a method context");
33416
+ }
33417
+ const { backtest: isBacktest } = bt.executionContextService.context;
33418
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33419
+ const levels = await bt.strategyCoreService.getPositionLevels(isBacktest, symbol, { exchangeName, frameName, strategyName });
33420
+ if (!levels) {
33421
+ return false;
33422
+ }
33423
+ return levels.some((level) => {
33424
+ const upperStep = (level * ladder.upperPercent) / 100;
33425
+ const lowerStep = (level * ladder.lowerPercent) / 100;
33426
+ return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
33427
+ });
33428
+ }
33429
+ /**
33430
+ * Checks whether the current price falls within the tolerance zone of any existing partial close price.
33431
+ * Use this to prevent duplicate partial closes at the same price area.
33432
+ *
33433
+ * Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
33434
+ * for any partial, where step = partial.currentPrice * percent / 100.
33435
+ * Returns false if no pending signal exists or no partials have been executed yet.
33436
+ *
33437
+ * @param symbol - Trading pair symbol
33438
+ * @param currentPrice - Price to check against existing partial close prices
33439
+ * @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
33440
+ * @returns Promise<boolean> - true if price overlaps an existing partial price (partial not recommended)
33441
+ *
33442
+ * @example
33443
+ * ```typescript
33444
+ * import { getPositionPartialOverlap } from "backtest-kit";
33445
+ *
33446
+ * // Partials at [45000], check if 45100 is too close
33447
+ * const overlap = await getPositionPartialOverlap("BTCUSDT", 45100, { upperPercent: 1.5, lowerPercent: 1.5 });
33448
+ * // overlap = true (45100 is within 1.5% of 45000)
33449
+ * if (!overlap) {
33450
+ * await commitPartialProfit("BTCUSDT", 50);
33451
+ * }
33452
+ * ```
33453
+ */
33454
+ async function getPositionPartialOverlap(symbol, currentPrice, ladder = POSITION_OVERLAP_LADDER_DEFAULT) {
33455
+ bt.loggerService.info(GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME, {
33456
+ symbol,
33457
+ currentPrice,
33458
+ ladder,
33459
+ });
33460
+ if (!ExecutionContextService.hasContext()) {
33461
+ throw new Error("getPositionPartialOverlap requires an execution context");
33462
+ }
33463
+ if (!MethodContextService.hasContext()) {
33464
+ throw new Error("getPositionPartialOverlap requires a method context");
33465
+ }
33466
+ const { backtest: isBacktest } = bt.executionContextService.context;
33467
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33468
+ const partials = await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
33469
+ if (!partials) {
33470
+ return false;
33471
+ }
33472
+ return partials.some((partial) => {
33473
+ const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
33474
+ const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
33475
+ return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
33476
+ });
33477
+ }
32847
33478
 
32848
33479
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
32849
33480
  /**
@@ -34070,6 +34701,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
34070
34701
  const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
34071
34702
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
34072
34703
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
34704
+ const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
34705
+ const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
34073
34706
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
34074
34707
  const BACKTEST_METHOD_NAME_CANCEL_SCHEDULED = "Backtest.commitCancelScheduled";
34075
34708
  const BACKTEST_METHOD_NAME_CLOSE_PENDING = "Backtest.commitClosePending";
@@ -34257,6 +34890,20 @@ class BacktestInstance {
34257
34890
  frameName: context.frameName,
34258
34891
  backtest: true,
34259
34892
  });
34893
+ bt.timeMetaService.clear({
34894
+ symbol,
34895
+ strategyName: context.strategyName,
34896
+ exchangeName: context.exchangeName,
34897
+ frameName: context.frameName,
34898
+ backtest: true,
34899
+ });
34900
+ bt.priceMetaService.clear({
34901
+ symbol,
34902
+ strategyName: context.strategyName,
34903
+ exchangeName: context.exchangeName,
34904
+ frameName: context.frameName,
34905
+ backtest: true,
34906
+ });
34260
34907
  }
34261
34908
  {
34262
34909
  const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
@@ -34860,6 +35507,90 @@ class BacktestUtils {
34860
35507
  }
34861
35508
  return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
34862
35509
  };
35510
+ /**
35511
+ * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
35512
+ * Use this to prevent duplicate DCA entries at the same price area.
35513
+ *
35514
+ * Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
35515
+ * where step = level * percent / 100.
35516
+ * Returns false if no pending signal exists.
35517
+ *
35518
+ * @param symbol - Trading pair symbol
35519
+ * @param currentPrice - Price to check against existing DCA levels
35520
+ * @param context - Execution context with strategyName, exchangeName, and frameName
35521
+ * @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
35522
+ * @returns true if price overlaps an existing entry level (DCA not recommended)
35523
+ */
35524
+ this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
35525
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
35526
+ symbol,
35527
+ currentPrice,
35528
+ context,
35529
+ ladder,
35530
+ });
35531
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
35532
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
35533
+ {
35534
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
35535
+ riskName &&
35536
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
35537
+ riskList &&
35538
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
35539
+ actions &&
35540
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
35541
+ }
35542
+ const levels = await bt.strategyCoreService.getPositionLevels(true, symbol, context);
35543
+ if (!levels) {
35544
+ return false;
35545
+ }
35546
+ return levels.some((level) => {
35547
+ const upperStep = (level * ladder.upperPercent) / 100;
35548
+ const lowerStep = (level * ladder.lowerPercent) / 100;
35549
+ return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
35550
+ });
35551
+ };
35552
+ /**
35553
+ * Checks whether the current price falls within the tolerance zone of any existing partial close price.
35554
+ * Use this to prevent duplicate partial closes at the same price area.
35555
+ *
35556
+ * Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
35557
+ * for any partial, where step = partial.currentPrice * percent / 100.
35558
+ * Returns false if no pending signal exists or no partials have been executed yet.
35559
+ *
35560
+ * @param symbol - Trading pair symbol
35561
+ * @param currentPrice - Price to check against existing partial close prices
35562
+ * @param context - Execution context with strategyName, exchangeName, and frameName
35563
+ * @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
35564
+ * @returns true if price overlaps an existing partial price (partial not recommended)
35565
+ */
35566
+ this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
35567
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
35568
+ symbol,
35569
+ currentPrice,
35570
+ context,
35571
+ ladder,
35572
+ });
35573
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
35574
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
35575
+ {
35576
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
35577
+ riskName &&
35578
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
35579
+ riskList &&
35580
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
35581
+ actions &&
35582
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
35583
+ }
35584
+ const partials = await bt.strategyCoreService.getPositionPartials(true, symbol, context);
35585
+ if (!partials) {
35586
+ return false;
35587
+ }
35588
+ return partials.some((partial) => {
35589
+ const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
35590
+ const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
35591
+ return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
35592
+ });
35593
+ };
34863
35594
  /**
34864
35595
  * Stops the strategy from generating new signals.
34865
35596
  *
@@ -35349,6 +36080,7 @@ class BacktestUtils {
35349
36080
  percentShift,
35350
36081
  currentPrice,
35351
36082
  newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
36083
+ takeProfitPrice: signal.priceTakeProfit,
35352
36084
  position: signal.position,
35353
36085
  context,
35354
36086
  backtest: true,
@@ -35433,6 +36165,7 @@ class BacktestUtils {
35433
36165
  percentShift,
35434
36166
  currentPrice,
35435
36167
  newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
36168
+ takeProfitPrice: signal.priceTakeProfit,
35436
36169
  position: signal.position,
35437
36170
  context,
35438
36171
  backtest: true,
@@ -35487,6 +36220,7 @@ class BacktestUtils {
35487
36220
  currentPrice,
35488
36221
  newStopLossPrice,
35489
36222
  position: signal.position,
36223
+ takeProfitPrice: signal.priceTakeProfit,
35490
36224
  context,
35491
36225
  backtest: true,
35492
36226
  });
@@ -35540,6 +36274,7 @@ class BacktestUtils {
35540
36274
  currentPrice,
35541
36275
  newTakeProfitPrice,
35542
36276
  position: signal.position,
36277
+ takeProfitPrice: signal.priceTakeProfit,
35543
36278
  context,
35544
36279
  backtest: true,
35545
36280
  });
@@ -35883,6 +36618,8 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
35883
36618
  const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
35884
36619
  const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
35885
36620
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
36621
+ const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
36622
+ const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
35886
36623
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
35887
36624
  const LIVE_METHOD_NAME_CANCEL_SCHEDULED = "Live.cancelScheduled";
35888
36625
  const LIVE_METHOD_NAME_CLOSE_PENDING = "Live.closePending";
@@ -36071,6 +36808,20 @@ class LiveInstance {
36071
36808
  frameName: "",
36072
36809
  backtest: false,
36073
36810
  });
36811
+ bt.timeMetaService.clear({
36812
+ symbol,
36813
+ strategyName: context.strategyName,
36814
+ exchangeName: context.exchangeName,
36815
+ frameName: "",
36816
+ backtest: false,
36817
+ });
36818
+ bt.priceMetaService.clear({
36819
+ symbol,
36820
+ strategyName: context.strategyName,
36821
+ exchangeName: context.exchangeName,
36822
+ frameName: "",
36823
+ backtest: false,
36824
+ });
36074
36825
  }
36075
36826
  {
36076
36827
  const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
@@ -36736,6 +37487,98 @@ class LiveUtils {
36736
37487
  frameName: "",
36737
37488
  });
36738
37489
  };
37490
+ /**
37491
+ * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
37492
+ * Use this to prevent duplicate DCA entries at the same price area.
37493
+ *
37494
+ * Returns true if currentPrice is within [level - lowerStep, level + upperStep] for any level,
37495
+ * where step = level * percent / 100.
37496
+ * Returns false if no pending signal exists.
37497
+ *
37498
+ * @param symbol - Trading pair symbol
37499
+ * @param currentPrice - Price to check against existing DCA levels
37500
+ * @param context - Execution context with strategyName and exchangeName
37501
+ * @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
37502
+ * @returns true if price overlaps an existing entry level (DCA not recommended)
37503
+ */
37504
+ this.getPositionEntryOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
37505
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP, {
37506
+ symbol,
37507
+ currentPrice,
37508
+ context,
37509
+ ladder,
37510
+ });
37511
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
37512
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
37513
+ {
37514
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37515
+ riskName &&
37516
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP);
37517
+ riskList &&
37518
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
37519
+ actions &&
37520
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP));
37521
+ }
37522
+ const levels = await bt.strategyCoreService.getPositionLevels(false, symbol, {
37523
+ strategyName: context.strategyName,
37524
+ exchangeName: context.exchangeName,
37525
+ frameName: "",
37526
+ });
37527
+ if (!levels) {
37528
+ return false;
37529
+ }
37530
+ return levels.some((level) => {
37531
+ const upperStep = (level * ladder.upperPercent) / 100;
37532
+ const lowerStep = (level * ladder.lowerPercent) / 100;
37533
+ return currentPrice >= level - lowerStep && currentPrice <= level + upperStep;
37534
+ });
37535
+ };
37536
+ /**
37537
+ * Checks whether the current price falls within the tolerance zone of any existing partial close price.
37538
+ * Use this to prevent duplicate partial closes at the same price area.
37539
+ *
37540
+ * Returns true if currentPrice is within [partial.currentPrice - lowerStep, partial.currentPrice + upperStep]
37541
+ * for any partial, where step = partial.currentPrice * percent / 100.
37542
+ * Returns false if no pending signal exists or no partials have been executed yet.
37543
+ *
37544
+ * @param symbol - Trading pair symbol
37545
+ * @param currentPrice - Price to check against existing partial close prices
37546
+ * @param context - Execution context with strategyName and exchangeName
37547
+ * @param ladder - Tolerance zone config; percentages in 0–100 format (default: 1.5% up and down)
37548
+ * @returns true if price overlaps an existing partial price (partial not recommended)
37549
+ */
37550
+ this.getPositionPartialOverlap = async (symbol, currentPrice, context, ladder = POSITION_OVERLAP_LADDER_DEFAULT) => {
37551
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP, {
37552
+ symbol,
37553
+ currentPrice,
37554
+ context,
37555
+ ladder,
37556
+ });
37557
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
37558
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
37559
+ {
37560
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37561
+ riskName &&
37562
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP);
37563
+ riskList &&
37564
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
37565
+ actions &&
37566
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP));
37567
+ }
37568
+ const partials = await bt.strategyCoreService.getPositionPartials(false, symbol, {
37569
+ strategyName: context.strategyName,
37570
+ exchangeName: context.exchangeName,
37571
+ frameName: "",
37572
+ });
37573
+ if (!partials) {
37574
+ return false;
37575
+ }
37576
+ return partials.some((partial) => {
37577
+ const upperStep = (partial.currentPrice * ladder.upperPercent) / 100;
37578
+ const lowerStep = (partial.currentPrice * ladder.lowerPercent) / 100;
37579
+ return currentPrice >= partial.currentPrice - lowerStep && currentPrice <= partial.currentPrice + upperStep;
37580
+ });
37581
+ };
36739
37582
  /**
36740
37583
  * Stops the strategy from generating new signals.
36741
37584
  *
@@ -37318,6 +38161,7 @@ class LiveUtils {
37318
38161
  percentShift,
37319
38162
  currentPrice,
37320
38163
  newStopLossPrice: slPercentShiftToPrice(percentShift, signal.priceStopLoss, effectivePriceOpen, signal.position),
38164
+ takeProfitPrice: signal.priceTakeProfit,
37321
38165
  position: signal.position,
37322
38166
  context,
37323
38167
  backtest: false,
@@ -37417,6 +38261,7 @@ class LiveUtils {
37417
38261
  percentShift,
37418
38262
  currentPrice,
37419
38263
  newTakeProfitPrice: tpPercentShiftToPrice(percentShift, signal.priceTakeProfit, effectivePriceOpen, signal.position),
38264
+ takeProfitPrice: signal.priceTakeProfit,
37420
38265
  position: signal.position,
37421
38266
  context,
37422
38267
  backtest: false,
@@ -37486,6 +38331,7 @@ class LiveUtils {
37486
38331
  percentShift,
37487
38332
  currentPrice,
37488
38333
  newStopLossPrice,
38334
+ takeProfitPrice: signal.priceTakeProfit,
37489
38335
  position: signal.position,
37490
38336
  context,
37491
38337
  backtest: false,
@@ -37555,6 +38401,7 @@ class LiveUtils {
37555
38401
  percentShift,
37556
38402
  currentPrice,
37557
38403
  newTakeProfitPrice,
38404
+ takeProfitPrice: signal.priceTakeProfit,
37558
38405
  position: signal.position,
37559
38406
  context,
37560
38407
  backtest: false,
@@ -40123,6 +40970,20 @@ class WalkerInstance {
40123
40970
  frameName: walkerSchema.frameName,
40124
40971
  backtest: true,
40125
40972
  });
40973
+ bt.timeMetaService.clear({
40974
+ symbol,
40975
+ strategyName,
40976
+ exchangeName: walkerSchema.exchangeName,
40977
+ frameName: walkerSchema.frameName,
40978
+ backtest: true,
40979
+ });
40980
+ bt.priceMetaService.clear({
40981
+ symbol,
40982
+ strategyName,
40983
+ exchangeName: walkerSchema.exchangeName,
40984
+ frameName: walkerSchema.frameName,
40985
+ backtest: true,
40986
+ });
40126
40987
  }
40127
40988
  {
40128
40989
  const { riskName, riskList, actions } = bt.strategySchemaService.get(strategyName);
@@ -43090,13 +43951,15 @@ class NotificationMemoryBacktestUtils {
43090
43951
  * Handles signal sync events (signal-open, signal-close).
43091
43952
  * @param data - The signal sync contract data
43092
43953
  */
43093
- this.handleSync = async (data) => {
43954
+ this.handleSync = trycatch(async (data) => {
43094
43955
  bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
43095
43956
  signalId: data.signalId,
43096
43957
  action: data.action,
43097
43958
  });
43098
43959
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43099
- };
43960
+ }, {
43961
+ defaultValue: null,
43962
+ });
43100
43963
  /**
43101
43964
  * Handles risk rejection event.
43102
43965
  * @param data - The risk contract data
@@ -43205,8 +44068,10 @@ class NotificationDummyBacktestUtils {
43205
44068
  /**
43206
44069
  * No-op handler for signal sync event.
43207
44070
  */
43208
- this.handleSync = async () => {
43209
- };
44071
+ this.handleSync = trycatch(async () => {
44072
+ }, {
44073
+ defaultValue: null,
44074
+ });
43210
44075
  /**
43211
44076
  * No-op handler for risk rejection event.
43212
44077
  */
@@ -43345,7 +44210,7 @@ class NotificationPersistBacktestUtils {
43345
44210
  * Handles signal sync events (signal-open, signal-close).
43346
44211
  * @param data - The signal sync contract data
43347
44212
  */
43348
- this.handleSync = async (data) => {
44213
+ this.handleSync = trycatch(async (data) => {
43349
44214
  bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
43350
44215
  signalId: data.signalId,
43351
44216
  action: data.action,
@@ -43353,7 +44218,9 @@ class NotificationPersistBacktestUtils {
43353
44218
  await this.waitForInit();
43354
44219
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43355
44220
  await this._updateNotifications();
43356
- };
44221
+ }, {
44222
+ defaultValue: null,
44223
+ });
43357
44224
  /**
43358
44225
  * Handles risk rejection event.
43359
44226
  * @param data - The risk contract data
@@ -43536,13 +44403,15 @@ class NotificationMemoryLiveUtils {
43536
44403
  * Handles signal sync events (signal-open, signal-close).
43537
44404
  * @param data - The signal sync contract data
43538
44405
  */
43539
- this.handleSync = async (data) => {
44406
+ this.handleSync = trycatch(async (data) => {
43540
44407
  bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SYNC, {
43541
44408
  signalId: data.signalId,
43542
44409
  action: data.action,
43543
44410
  });
43544
44411
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43545
- };
44412
+ }, {
44413
+ defaultValue: null,
44414
+ });
43546
44415
  /**
43547
44416
  * Handles risk rejection event.
43548
44417
  * @param data - The risk contract data
@@ -43651,8 +44520,10 @@ class NotificationDummyLiveUtils {
43651
44520
  /**
43652
44521
  * No-op handler for signal sync event.
43653
44522
  */
43654
- this.handleSync = async () => {
43655
- };
44523
+ this.handleSync = trycatch(async () => {
44524
+ }, {
44525
+ defaultValue: null,
44526
+ });
43656
44527
  /**
43657
44528
  * No-op handler for risk rejection event.
43658
44529
  */
@@ -43792,7 +44663,7 @@ class NotificationPersistLiveUtils {
43792
44663
  * Handles signal sync events (signal-open, signal-close).
43793
44664
  * @param data - The signal sync contract data
43794
44665
  */
43795
- this.handleSync = async (data) => {
44666
+ this.handleSync = trycatch(async (data) => {
43796
44667
  bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SYNC, {
43797
44668
  signalId: data.signalId,
43798
44669
  action: data.action,
@@ -43800,7 +44671,9 @@ class NotificationPersistLiveUtils {
43800
44671
  await this.waitForInit();
43801
44672
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43802
44673
  await this._updateNotifications();
43803
- };
44674
+ }, {
44675
+ defaultValue: null,
44676
+ });
43804
44677
  /**
43805
44678
  * Handles risk rejection event.
43806
44679
  * @param data - The risk contract data
@@ -43962,9 +44835,11 @@ class NotificationBacktestAdapter {
43962
44835
  * Proxies call to the underlying notification adapter.
43963
44836
  * @param data - The signal sync contract data
43964
44837
  */
43965
- this.handleSync = async (data) => {
44838
+ this.handleSync = trycatch(async (data) => {
43966
44839
  return await this._notificationBacktestUtils.handleSync(data);
43967
- };
44840
+ }, {
44841
+ defaultValue: null,
44842
+ });
43968
44843
  /**
43969
44844
  * Handles risk rejection event.
43970
44845
  * Proxies call to the underlying notification adapter.
@@ -44106,9 +44981,11 @@ class NotificationLiveAdapter {
44106
44981
  * Proxies call to the underlying notification adapter.
44107
44982
  * @param data - The signal sync contract data
44108
44983
  */
44109
- this.handleSync = async (data) => {
44984
+ this.handleSync = trycatch(async (data) => {
44110
44985
  return await this._notificationLiveUtils.handleSync(data);
44111
- };
44986
+ }, {
44987
+ defaultValue: null,
44988
+ });
44112
44989
  /**
44113
44990
  * Handles risk rejection event.
44114
44991
  * Proxies call to the underlying notification adapter.
@@ -45465,4 +46342,4 @@ const percentValue = (yesterdayValue, todayValue) => {
45465
46342
  return yesterdayValue / todayValue - 1;
45466
46343
  };
45467
46344
 
45468
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
46345
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionEntryOverlap, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };