backtest-kit 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -89,6 +89,10 @@ const coreServices$1 = {
89
89
  actionCoreService: Symbol('actionCoreService'),
90
90
  frameCoreService: Symbol('frameCoreService'),
91
91
  };
92
+ const metaServices$1 = {
93
+ priceMetaService: Symbol('priceMetaService'),
94
+ timeMetaService: Symbol('timeMetaService'),
95
+ };
92
96
  const globalServices$1 = {
93
97
  sizingGlobalService: Symbol('sizingGlobalService'),
94
98
  riskGlobalService: Symbol('riskGlobalService'),
@@ -154,6 +158,7 @@ const TYPES = {
154
158
  ...connectionServices$1,
155
159
  ...schemaServices$1,
156
160
  ...coreServices$1,
161
+ ...metaServices$1,
157
162
  ...globalServices$1,
158
163
  ...commandServices$1,
159
164
  ...logicPrivateServices$1,
@@ -3558,19 +3563,6 @@ const beginTime = (run) => (...args) => {
3558
3563
  return fn();
3559
3564
  };
3560
3565
 
3561
- /**
3562
- * Retrieves the current timestamp for debugging purposes.
3563
- * 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.
3564
- * Can be empty (undefined) if not called from strategy async context, as it's intended for debugging and not critical for logic.
3565
- * @return {number | undefined} The current timestamp in milliseconds from the execution context, or undefined if not available.
3566
- */
3567
- const getDebugTimestamp = () => {
3568
- if (ExecutionContextService.hasContext()) {
3569
- return bt.executionContextService.context.when.getTime();
3570
- }
3571
- return undefined;
3572
- };
3573
-
3574
3566
  const INTERVAL_MINUTES$6 = {
3575
3567
  "1m": 1,
3576
3568
  "3m": 3,
@@ -4278,7 +4270,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
4278
4270
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
4279
4271
  timestamp: currentTime,
4280
4272
  _isScheduled: false,
4281
- _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: currentTime }],
4273
+ _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4282
4274
  };
4283
4275
  // Валидируем сигнал перед возвратом
4284
4276
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4302,7 +4294,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
4302
4294
  pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
4303
4295
  timestamp: currentTime,
4304
4296
  _isScheduled: true,
4305
- _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: currentTime }],
4297
+ _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4306
4298
  };
4307
4299
  // Валидируем сигнал перед возвратом
4308
4300
  VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
@@ -4322,7 +4314,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
4322
4314
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
4323
4315
  timestamp: currentTime,
4324
4316
  _isScheduled: false,
4325
- _entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: currentTime }],
4317
+ _entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4326
4318
  };
4327
4319
  // Валидируем сигнал перед возвратом
4328
4320
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4392,7 +4384,7 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
4392
4384
  self.params.logger.debug("ClientStrategy dispose");
4393
4385
  await self.params.onDispose(self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName, self.params.method.context.frameName, self.params.execution.context.backtest);
4394
4386
  };
4395
- const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4387
+ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
4396
4388
  // Initialize partial array if not present
4397
4389
  if (!signal._partial)
4398
4390
  signal._partial = [];
@@ -4421,7 +4413,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4421
4413
  entryCountAtClose,
4422
4414
  currentPrice,
4423
4415
  costBasisAtClose: remainingCostBasis,
4424
- debugTimestamp: getDebugTimestamp(),
4416
+ timestamp,
4425
4417
  });
4426
4418
  self.params.logger.info("PARTIAL_PROFIT_FN executed", {
4427
4419
  signalId: signal.id,
@@ -4431,7 +4423,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4431
4423
  });
4432
4424
  return true;
4433
4425
  };
4434
- const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
4426
+ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
4435
4427
  // Initialize partial array if not present
4436
4428
  if (!signal._partial)
4437
4429
  signal._partial = [];
@@ -4459,7 +4451,7 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
4459
4451
  currentPrice,
4460
4452
  entryCountAtClose,
4461
4453
  costBasisAtClose: remainingCostBasis,
4462
- debugTimestamp: getDebugTimestamp(),
4454
+ timestamp,
4463
4455
  });
4464
4456
  self.params.logger.warn("PARTIAL_LOSS_FN executed", {
4465
4457
  signalId: signal.id,
@@ -4856,10 +4848,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4856
4848
  });
4857
4849
  return true;
4858
4850
  };
4859
- const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
4851
+ const AVERAGE_BUY_FN = (self, signal, currentPrice, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) => {
4860
4852
  // Ensure _entry is initialized (handles signals loaded from disk without _entry)
4861
4853
  if (!signal._entry || signal._entry.length === 0) {
4862
- signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, debugTimestamp: getDebugTimestamp() }];
4854
+ signal._entry = [{ price: signal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
4863
4855
  }
4864
4856
  if (signal.position === "long") {
4865
4857
  // LONG: new entry must beat the all-time low — strictly below every prior entry price
@@ -4889,7 +4881,7 @@ const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSI
4889
4881
  return false;
4890
4882
  }
4891
4883
  }
4892
- signal._entry.push({ price: currentPrice, cost, debugTimestamp: getDebugTimestamp() });
4884
+ signal._entry.push({ price: currentPrice, cost, timestamp });
4893
4885
  self.params.logger.info("AVERAGE_BUY_FN executed", {
4894
4886
  signalId: signal.id,
4895
4887
  position: signal.position,
@@ -6674,16 +6666,16 @@ class ClientStrategy {
6674
6666
  * // No DCA: [{ price: 43000, cost: 100 }]
6675
6667
  * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
6676
6668
  */
6677
- async getPositionEntries(symbol) {
6669
+ async getPositionEntries(symbol, timestamp) {
6678
6670
  this.params.logger.debug("ClientStrategy getPositionEntries", { symbol });
6679
6671
  if (!this._pendingSignal) {
6680
6672
  return null;
6681
6673
  }
6682
6674
  const entries = this._pendingSignal._entry;
6683
6675
  if (!entries || entries.length === 0) {
6684
- return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST }];
6676
+ return [{ price: this._pendingSignal.priceOpen, cost: GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp }];
6685
6677
  }
6686
- return entries.map(({ price, cost }) => ({ price, cost }));
6678
+ return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
6687
6679
  }
6688
6680
  /**
6689
6681
  * Performs a single tick of strategy execution.
@@ -7519,7 +7511,7 @@ class ClientStrategy {
7519
7511
  * // success3 = false (skipped, would exceed 100%)
7520
7512
  * ```
7521
7513
  */
7522
- async partialProfit(symbol, percentToClose, currentPrice, backtest) {
7514
+ async partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp) {
7523
7515
  this.params.logger.debug("ClientStrategy partialProfit", {
7524
7516
  symbol,
7525
7517
  percentToClose,
@@ -7583,7 +7575,7 @@ class ClientStrategy {
7583
7575
  return false;
7584
7576
  }
7585
7577
  // Execute partial close logic
7586
- const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice);
7578
+ const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
7587
7579
  // If partial was not executed (exceeded 100%), return false without persistence
7588
7580
  if (!wasExecuted) {
7589
7581
  return false;
@@ -7702,7 +7694,7 @@ class ClientStrategy {
7702
7694
  * // success3 = false (skipped, would exceed 100%)
7703
7695
  * ```
7704
7696
  */
7705
- async partialLoss(symbol, percentToClose, currentPrice, backtest) {
7697
+ async partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp) {
7706
7698
  this.params.logger.debug("ClientStrategy partialLoss", {
7707
7699
  symbol,
7708
7700
  percentToClose,
@@ -7766,7 +7758,7 @@ class ClientStrategy {
7766
7758
  return false;
7767
7759
  }
7768
7760
  // Execute partial close logic
7769
- const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice);
7761
+ const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
7770
7762
  // If partial was not executed (exceeded 100%), return false without persistence
7771
7763
  if (!wasExecuted) {
7772
7764
  return false;
@@ -8522,7 +8514,7 @@ class ClientStrategy {
8522
8514
  * @param backtest - Whether running in backtest mode
8523
8515
  * @returns Promise<boolean> - true if entry added, false if rejected by direction check
8524
8516
  */
8525
- async averageBuy(symbol, currentPrice, backtest, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
8517
+ async averageBuy(symbol, currentPrice, backtest, timestamp, cost = GLOBAL_CONFIG.CC_POSITION_ENTRY_COST) {
8526
8518
  this.params.logger.debug("ClientStrategy averageBuy", {
8527
8519
  symbol,
8528
8520
  currentPrice,
@@ -8537,7 +8529,7 @@ class ClientStrategy {
8537
8529
  throw new Error(`ClientStrategy averageBuy: currentPrice must be a positive finite number, got ${currentPrice}`);
8538
8530
  }
8539
8531
  // Execute averaging logic
8540
- const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, cost);
8532
+ const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, timestamp, cost);
8541
8533
  if (!result) {
8542
8534
  return false;
8543
8535
  }
@@ -9001,7 +8993,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
9001
8993
  * @param backtest - Whether running in backtest mode
9002
8994
  * @returns Unique string key for memoization
9003
8995
  */
9004
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
8996
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
9005
8997
  const parts = [symbol, strategyName, exchangeName];
9006
8998
  if (frameName)
9007
8999
  parts.push(frameName);
@@ -9178,6 +9170,8 @@ class StrategyConnectionService {
9178
9170
  this.partialConnectionService = inject(TYPES.partialConnectionService);
9179
9171
  this.breakevenConnectionService = inject(TYPES.breakevenConnectionService);
9180
9172
  this.actionCoreService = inject(TYPES.actionCoreService);
9173
+ this.timeMetaService = inject(TYPES.timeMetaService);
9174
+ this.priceMetaService = inject(TYPES.priceMetaService);
9181
9175
  /**
9182
9176
  * Retrieves memoized ClientStrategy instance for given symbol-strategy pair with exchange and frame isolation.
9183
9177
  *
@@ -9191,7 +9185,7 @@ class StrategyConnectionService {
9191
9185
  * @param backtest - Whether running in backtest mode
9192
9186
  * @returns Configured ClientStrategy instance
9193
9187
  */
9194
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9188
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9195
9189
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
9196
9190
  return new ClientStrategy({
9197
9191
  symbol,
@@ -9278,6 +9272,20 @@ class StrategyConnectionService {
9278
9272
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9279
9273
  return await strategy.getTotalCostClosed(symbol);
9280
9274
  };
9275
+ /**
9276
+ * Returns the effective (DCA-averaged) entry price for the current pending signal.
9277
+ *
9278
+ * This is the harmonic mean of all _entry prices, which is the correct
9279
+ * cost-basis price used in all PNL calculations.
9280
+ * With no DCA entries, equals the original priceOpen.
9281
+ *
9282
+ * Returns null if no pending signal exists.
9283
+ *
9284
+ * @param backtest - Whether running in backtest mode
9285
+ * @param symbol - Trading pair symbol
9286
+ * @param context - Execution context with strategyName, exchangeName, frameName
9287
+ * @returns Promise resolving to effective entry price or null
9288
+ */
9281
9289
  this.getPositionAveragePrice = async (backtest, symbol, context) => {
9282
9290
  this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
9283
9291
  symbol,
@@ -9287,6 +9295,19 @@ class StrategyConnectionService {
9287
9295
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9288
9296
  return await strategy.getPositionAveragePrice(symbol);
9289
9297
  };
9298
+ /**
9299
+ * Returns the number of DCA entries made for the current pending signal.
9300
+ *
9301
+ * 1 = original entry only (no DCA).
9302
+ * Increases by 1 with each successful commitAverageBuy().
9303
+ *
9304
+ * Returns null if no pending signal exists.
9305
+ *
9306
+ * @param backtest - Whether running in backtest mode
9307
+ * @param symbol - Trading pair symbol
9308
+ * @param context - Execution context with strategyName, exchangeName, frameName
9309
+ * @returns Promise resolving to entry count or null
9310
+ */
9290
9311
  this.getPositionInvestedCount = async (backtest, symbol, context) => {
9291
9312
  this.loggerService.log("strategyConnectionService getPositionInvestedCount", {
9292
9313
  symbol,
@@ -9296,6 +9317,19 @@ class StrategyConnectionService {
9296
9317
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9297
9318
  return await strategy.getPositionInvestedCount(symbol);
9298
9319
  };
9320
+ /**
9321
+ * Returns the total invested cost basis in dollars for the current pending signal.
9322
+ *
9323
+ * Equal to entryCount × $100 (COST_BASIS_PER_ENTRY).
9324
+ * 1 entry = $100, 2 entries = $200, etc.
9325
+ *
9326
+ * Returns null if no pending signal exists.
9327
+ *
9328
+ * @param backtest - Whether running in backtest mode
9329
+ * @param symbol - Trading pair symbol
9330
+ * @param context - Execution context with strategyName, exchangeName, frameName
9331
+ * @returns Promise resolving to total invested cost in dollars or null
9332
+ */
9299
9333
  this.getPositionInvestedCost = async (backtest, symbol, context) => {
9300
9334
  this.loggerService.log("strategyConnectionService getPositionInvestedCost", {
9301
9335
  symbol,
@@ -9305,6 +9339,20 @@ class StrategyConnectionService {
9305
9339
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9306
9340
  return await strategy.getPositionInvestedCost(symbol);
9307
9341
  };
9342
+ /**
9343
+ * Returns the unrealized PNL percentage for the current pending signal at currentPrice.
9344
+ *
9345
+ * Accounts for partial closes, DCA entries, slippage and fees
9346
+ * (delegates to toProfitLossDto).
9347
+ *
9348
+ * Returns null if no pending signal exists.
9349
+ *
9350
+ * @param backtest - Whether running in backtest mode
9351
+ * @param symbol - Trading pair symbol
9352
+ * @param currentPrice - Current market price
9353
+ * @param context - Execution context with strategyName, exchangeName, frameName
9354
+ * @returns Promise resolving to pnlPercentage or null
9355
+ */
9308
9356
  this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
9309
9357
  this.loggerService.log("strategyConnectionService getPositionPnlPercent", {
9310
9358
  symbol,
@@ -9315,6 +9363,20 @@ class StrategyConnectionService {
9315
9363
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9316
9364
  return await strategy.getPositionPnlPercent(symbol, currentPrice);
9317
9365
  };
9366
+ /**
9367
+ * Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
9368
+ *
9369
+ * Calculated as: pnlPercentage / 100 × totalInvestedCost
9370
+ * Accounts for partial closes, DCA entries, slippage and fees.
9371
+ *
9372
+ * Returns null if no pending signal exists.
9373
+ *
9374
+ * @param backtest - Whether running in backtest mode
9375
+ * @param symbol - Trading pair symbol
9376
+ * @param currentPrice - Current market price
9377
+ * @param context - Execution context with strategyName, exchangeName, frameName
9378
+ * @returns Promise resolving to pnl in dollars or null
9379
+ */
9318
9380
  this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
9319
9381
  this.loggerService.log("strategyConnectionService getPositionPnlCost", {
9320
9382
  symbol,
@@ -9325,6 +9387,27 @@ class StrategyConnectionService {
9325
9387
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9326
9388
  return await strategy.getPositionPnlCost(symbol, currentPrice);
9327
9389
  };
9390
+ /**
9391
+ * Returns the list of DCA entry prices for the current pending signal.
9392
+ *
9393
+ * The first element is always the original priceOpen (initial entry).
9394
+ * Each subsequent element is a price added by commitAverageBuy().
9395
+ *
9396
+ * Returns null if no pending signal exists.
9397
+ * Returns a single-element array [priceOpen] if no DCA entries were made.
9398
+ *
9399
+ * @param backtest - Whether running in backtest mode
9400
+ * @param symbol - Trading pair symbol
9401
+ * @param context - Execution context with strategyName, exchangeName, frameName
9402
+ * @returns Promise resolving to array of entry prices or null
9403
+ *
9404
+ * @example
9405
+ * ```typescript
9406
+ * // No DCA: [43000]
9407
+ * // One DCA: [43000, 42000]
9408
+ * // Two DCA: [43000, 42000, 41500]
9409
+ * ```
9410
+ */
9328
9411
  this.getPositionLevels = async (backtest, symbol, context) => {
9329
9412
  this.loggerService.log("strategyConnectionService getPositionLevels", {
9330
9413
  symbol,
@@ -9334,6 +9417,20 @@ class StrategyConnectionService {
9334
9417
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9335
9418
  return await strategy.getPositionLevels(symbol);
9336
9419
  };
9420
+ /**
9421
+ * Returns the list of partial closes for the current pending signal.
9422
+ *
9423
+ * Each entry records a partial profit or loss close event with its type,
9424
+ * percent closed, price at close, cost basis snapshot, and entry count at close.
9425
+ *
9426
+ * Returns null if no pending signal exists.
9427
+ * Returns an empty array if no partial closes have been executed.
9428
+ *
9429
+ * @param backtest - Whether running in backtest mode
9430
+ * @param symbol - Trading pair symbol
9431
+ * @param context - Execution context with strategyName, exchangeName, frameName
9432
+ * @returns Promise resolving to array of partial close records or null
9433
+ */
9337
9434
  this.getPositionPartials = async (backtest, symbol, context) => {
9338
9435
  this.loggerService.log("strategyConnectionService getPositionPartials", {
9339
9436
  symbol,
@@ -9343,6 +9440,27 @@ class StrategyConnectionService {
9343
9440
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9344
9441
  return await strategy.getPositionPartials(symbol);
9345
9442
  };
9443
+ /**
9444
+ * Returns the list of DCA entry prices and costs for the current pending signal.
9445
+ *
9446
+ * Each entry records the price and cost of a single position entry.
9447
+ * The first element is always the original priceOpen (initial entry).
9448
+ * Each subsequent element is an entry added by averageBuy().
9449
+ *
9450
+ * Returns null if no pending signal exists.
9451
+ * Returns a single-element array [{ price: priceOpen, cost }] if no DCA entries were made.
9452
+ *
9453
+ * @param backtest - Whether running in backtest mode
9454
+ * @param symbol - Trading pair symbol
9455
+ * @param context - Execution context with strategyName, exchangeName, frameName
9456
+ * @returns Promise resolving to array of entry records or null
9457
+ *
9458
+ * @example
9459
+ * ```typescript
9460
+ * // No DCA: [{ price: 43000, cost: 100 }]
9461
+ * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
9462
+ * ```
9463
+ */
9346
9464
  this.getPositionEntries = async (backtest, symbol, context) => {
9347
9465
  this.loggerService.log("strategyConnectionService getPositionEntries", {
9348
9466
  symbol,
@@ -9350,7 +9468,8 @@ class StrategyConnectionService {
9350
9468
  backtest,
9351
9469
  });
9352
9470
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9353
- return await strategy.getPositionEntries(symbol);
9471
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9472
+ return await strategy.getPositionEntries(symbol, timestamp);
9354
9473
  };
9355
9474
  /**
9356
9475
  * Retrieves the currently active scheduled signal for the strategy.
@@ -9452,6 +9571,10 @@ class StrategyConnectionService {
9452
9571
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9453
9572
  await strategy.waitForInit();
9454
9573
  const tick = await strategy.tick(symbol, context.strategyName);
9574
+ {
9575
+ this.priceMetaService.next(symbol, tick.currentPrice, context, backtest);
9576
+ this.timeMetaService.next(symbol, tick.createdAt, context, backtest);
9577
+ }
9455
9578
  {
9456
9579
  await CALL_SIGNAL_EMIT_FN(this, tick, context, backtest, symbol);
9457
9580
  }
@@ -9558,7 +9681,7 @@ class StrategyConnectionService {
9558
9681
  }
9559
9682
  return;
9560
9683
  }
9561
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9684
+ const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9562
9685
  if (!this.getStrategy.has(key)) {
9563
9686
  return;
9564
9687
  }
@@ -9627,9 +9750,9 @@ class StrategyConnectionService {
9627
9750
  * @param context - Execution context with strategyName, exchangeName, frameName
9628
9751
  * @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
9629
9752
  */
9630
- this.validatePartialProfit = (backtest, symbol, percentToClose, currentPrice, context) => {
9753
+ this.validatePartialProfit = async (backtest, symbol, percentToClose, currentPrice, context) => {
9631
9754
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9632
- return Promise.resolve(strategy.validatePartialProfit(symbol, percentToClose, currentPrice));
9755
+ return await strategy.validatePartialProfit(symbol, percentToClose, currentPrice);
9633
9756
  };
9634
9757
  /**
9635
9758
  * Executes partial close at profit level (moving toward TP).
@@ -9670,7 +9793,8 @@ class StrategyConnectionService {
9670
9793
  backtest,
9671
9794
  });
9672
9795
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9673
- return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest);
9796
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9797
+ return await strategy.partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp);
9674
9798
  };
9675
9799
  /**
9676
9800
  * Checks whether `partialLoss` would succeed without executing it.
@@ -9683,9 +9807,9 @@ class StrategyConnectionService {
9683
9807
  * @param context - Execution context with strategyName, exchangeName, frameName
9684
9808
  * @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
9685
9809
  */
9686
- this.validatePartialLoss = (backtest, symbol, percentToClose, currentPrice, context) => {
9810
+ this.validatePartialLoss = async (backtest, symbol, percentToClose, currentPrice, context) => {
9687
9811
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9688
- return Promise.resolve(strategy.validatePartialLoss(symbol, percentToClose, currentPrice));
9812
+ return await strategy.validatePartialLoss(symbol, percentToClose, currentPrice);
9689
9813
  };
9690
9814
  /**
9691
9815
  * Executes partial close at loss level (moving toward SL).
@@ -9726,7 +9850,8 @@ class StrategyConnectionService {
9726
9850
  backtest,
9727
9851
  });
9728
9852
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9729
- return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest);
9853
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9854
+ return await strategy.partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp);
9730
9855
  };
9731
9856
  /**
9732
9857
  * Checks whether `trailingStop` would succeed without executing it.
@@ -9739,9 +9864,9 @@ class StrategyConnectionService {
9739
9864
  * @param context - Execution context with strategyName, exchangeName, frameName
9740
9865
  * @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
9741
9866
  */
9742
- this.validateTrailingStop = (backtest, symbol, percentShift, currentPrice, context) => {
9867
+ this.validateTrailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
9743
9868
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9744
- return Promise.resolve(strategy.validateTrailingStop(symbol, percentShift, currentPrice));
9869
+ return await strategy.validateTrailingStop(symbol, percentShift, currentPrice);
9745
9870
  };
9746
9871
  /**
9747
9872
  * Adjusts the trailing stop-loss distance for an active pending signal.
@@ -9793,9 +9918,9 @@ class StrategyConnectionService {
9793
9918
  * @param context - Execution context with strategyName, exchangeName, frameName
9794
9919
  * @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
9795
9920
  */
9796
- this.validateTrailingTake = (backtest, symbol, percentShift, currentPrice, context) => {
9921
+ this.validateTrailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
9797
9922
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9798
- return Promise.resolve(strategy.validateTrailingTake(symbol, percentShift, currentPrice));
9923
+ return await strategy.validateTrailingTake(symbol, percentShift, currentPrice);
9799
9924
  };
9800
9925
  /**
9801
9926
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -9846,9 +9971,9 @@ class StrategyConnectionService {
9846
9971
  * @param context - Execution context with strategyName, exchangeName, frameName
9847
9972
  * @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
9848
9973
  */
9849
- this.validateBreakeven = (backtest, symbol, currentPrice, context) => {
9974
+ this.validateBreakeven = async (backtest, symbol, currentPrice, context) => {
9850
9975
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9851
- return Promise.resolve(strategy.validateBreakeven(symbol, currentPrice));
9976
+ return await strategy.validateBreakeven(symbol, currentPrice);
9852
9977
  };
9853
9978
  /**
9854
9979
  * Delegates to ClientStrategy.breakeven() with current execution context.
@@ -9925,9 +10050,9 @@ class StrategyConnectionService {
9925
10050
  * @param context - Execution context with strategyName, exchangeName, frameName
9926
10051
  * @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
9927
10052
  */
9928
- this.validateAverageBuy = (backtest, symbol, currentPrice, context) => {
10053
+ this.validateAverageBuy = async (backtest, symbol, currentPrice, context) => {
9929
10054
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9930
- return Promise.resolve(strategy.validateAverageBuy(symbol, currentPrice));
10055
+ return await strategy.validateAverageBuy(symbol, currentPrice);
9931
10056
  };
9932
10057
  /**
9933
10058
  * Adds a new DCA entry to the active pending signal.
@@ -9948,7 +10073,8 @@ class StrategyConnectionService {
9948
10073
  backtest,
9949
10074
  });
9950
10075
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9951
- return await strategy.averageBuy(symbol, currentPrice, backtest, cost);
10076
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
10077
+ return await strategy.averageBuy(symbol, currentPrice, backtest, timestamp, cost);
9952
10078
  };
9953
10079
  }
9954
10080
  }
@@ -10689,7 +10815,7 @@ class ClientRisk {
10689
10815
  * @param backtest - Whether running in backtest mode
10690
10816
  * @returns Unique string key for memoization
10691
10817
  */
10692
- const CREATE_KEY_FN$l = (riskName, exchangeName, frameName, backtest) => {
10818
+ const CREATE_KEY_FN$n = (riskName, exchangeName, frameName, backtest) => {
10693
10819
  const parts = [riskName, exchangeName];
10694
10820
  if (frameName)
10695
10821
  parts.push(frameName);
@@ -10788,7 +10914,7 @@ class RiskConnectionService {
10788
10914
  * @param backtest - True if backtest mode, false if live mode
10789
10915
  * @returns Configured ClientRisk instance
10790
10916
  */
10791
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
10917
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
10792
10918
  const schema = this.riskSchemaService.get(riskName);
10793
10919
  return new ClientRisk({
10794
10920
  ...schema,
@@ -10856,7 +10982,7 @@ class RiskConnectionService {
10856
10982
  payload,
10857
10983
  });
10858
10984
  if (payload) {
10859
- const key = CREATE_KEY_FN$l(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
10985
+ const key = CREATE_KEY_FN$n(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
10860
10986
  this.getRisk.clear(key);
10861
10987
  }
10862
10988
  else {
@@ -12323,7 +12449,7 @@ class ClientAction {
12323
12449
  * @param backtest - Whether running in backtest mode
12324
12450
  * @returns Unique string key for memoization
12325
12451
  */
12326
- const CREATE_KEY_FN$k = (actionName, strategyName, exchangeName, frameName, backtest) => {
12452
+ const CREATE_KEY_FN$m = (actionName, strategyName, exchangeName, frameName, backtest) => {
12327
12453
  const parts = [actionName, strategyName, exchangeName];
12328
12454
  if (frameName)
12329
12455
  parts.push(frameName);
@@ -12374,7 +12500,7 @@ class ActionConnectionService {
12374
12500
  * @param backtest - True if backtest mode, false if live mode
12375
12501
  * @returns Configured ClientAction instance
12376
12502
  */
12377
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
12503
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
12378
12504
  const schema = this.actionSchemaService.get(actionName);
12379
12505
  return new ClientAction({
12380
12506
  ...schema,
@@ -12584,7 +12710,7 @@ class ActionConnectionService {
12584
12710
  await Promise.all(actions.map(async (action) => await action.dispose()));
12585
12711
  return;
12586
12712
  }
12587
- const key = CREATE_KEY_FN$k(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
12713
+ const key = CREATE_KEY_FN$m(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
12588
12714
  if (!this.getAction.has(key)) {
12589
12715
  return;
12590
12716
  }
@@ -12602,7 +12728,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
12602
12728
  * @param exchangeName - Exchange name
12603
12729
  * @returns Unique string key for memoization
12604
12730
  */
12605
- const CREATE_KEY_FN$j = (exchangeName) => {
12731
+ const CREATE_KEY_FN$l = (exchangeName) => {
12606
12732
  return exchangeName;
12607
12733
  };
12608
12734
  /**
@@ -12626,7 +12752,7 @@ class ExchangeCoreService {
12626
12752
  * @param exchangeName - Name of the exchange to validate
12627
12753
  * @returns Promise that resolves when validation is complete
12628
12754
  */
12629
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$j(exchangeName), async (exchangeName) => {
12755
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$l(exchangeName), async (exchangeName) => {
12630
12756
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
12631
12757
  exchangeName,
12632
12758
  });
@@ -12878,7 +13004,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
12878
13004
  * @param context - Execution context with strategyName, exchangeName, frameName
12879
13005
  * @returns Unique string key for memoization
12880
13006
  */
12881
- const CREATE_KEY_FN$i = (context) => {
13007
+ const CREATE_KEY_FN$k = (context) => {
12882
13008
  const parts = [context.strategyName, context.exchangeName];
12883
13009
  if (context.frameName)
12884
13010
  parts.push(context.frameName);
@@ -12910,7 +13036,7 @@ class StrategyCoreService {
12910
13036
  * @param context - Execution context with strategyName, exchangeName, frameName
12911
13037
  * @returns Promise that resolves when validation is complete
12912
13038
  */
12913
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
13039
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
12914
13040
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
12915
13041
  context,
12916
13042
  });
@@ -13745,7 +13871,7 @@ class SizingGlobalService {
13745
13871
  * @param context - Context with riskName, exchangeName, frameName
13746
13872
  * @returns Unique string key for memoization
13747
13873
  */
13748
- const CREATE_KEY_FN$h = (context) => {
13874
+ const CREATE_KEY_FN$j = (context) => {
13749
13875
  const parts = [context.riskName, context.exchangeName];
13750
13876
  if (context.frameName)
13751
13877
  parts.push(context.frameName);
@@ -13771,7 +13897,7 @@ class RiskGlobalService {
13771
13897
  * @param payload - Payload with riskName, exchangeName and frameName
13772
13898
  * @returns Promise that resolves when validation is complete
13773
13899
  */
13774
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
13900
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
13775
13901
  this.loggerService.log("riskGlobalService validate", {
13776
13902
  context,
13777
13903
  });
@@ -13849,7 +13975,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
13849
13975
  * @param context - Execution context with strategyName, exchangeName, frameName
13850
13976
  * @returns Unique string key for memoization
13851
13977
  */
13852
- const CREATE_KEY_FN$g = (context) => {
13978
+ const CREATE_KEY_FN$i = (context) => {
13853
13979
  const parts = [context.strategyName, context.exchangeName];
13854
13980
  if (context.frameName)
13855
13981
  parts.push(context.frameName);
@@ -13893,7 +14019,7 @@ class ActionCoreService {
13893
14019
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
13894
14020
  * @returns Promise that resolves when all validations complete
13895
14021
  */
13896
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
14022
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
13897
14023
  this.loggerService.log(METHOD_NAME_VALIDATE, {
13898
14024
  context,
13899
14025
  });
@@ -18428,7 +18554,7 @@ const Markdown = new MarkdownAdapter();
18428
18554
  * @param backtest - Whether running in backtest mode
18429
18555
  * @returns Unique string key for memoization
18430
18556
  */
18431
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
18557
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
18432
18558
  const parts = [symbol, strategyName, exchangeName];
18433
18559
  if (frameName)
18434
18560
  parts.push(frameName);
@@ -18667,7 +18793,7 @@ class BacktestMarkdownService {
18667
18793
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
18668
18794
  * Each combination gets its own isolated storage instance.
18669
18795
  */
18670
- this.getStorage = functoolsKit.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));
18796
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
18671
18797
  /**
18672
18798
  * Processes tick events and accumulates closed signals.
18673
18799
  * Should be called from IStrategyCallbacks.onTick.
@@ -18824,7 +18950,7 @@ class BacktestMarkdownService {
18824
18950
  payload,
18825
18951
  });
18826
18952
  if (payload) {
18827
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
18953
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
18828
18954
  this.getStorage.clear(key);
18829
18955
  }
18830
18956
  else {
@@ -18886,7 +19012,7 @@ class BacktestMarkdownService {
18886
19012
  * @param backtest - Whether running in backtest mode
18887
19013
  * @returns Unique string key for memoization
18888
19014
  */
18889
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
19015
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
18890
19016
  const parts = [symbol, strategyName, exchangeName];
18891
19017
  if (frameName)
18892
19018
  parts.push(frameName);
@@ -19369,7 +19495,7 @@ class LiveMarkdownService {
19369
19495
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19370
19496
  * Each combination gets its own isolated storage instance.
19371
19497
  */
19372
- this.getStorage = functoolsKit.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));
19498
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
19373
19499
  /**
19374
19500
  * Subscribes to live signal emitter to receive tick events.
19375
19501
  * Protected against multiple subscriptions.
@@ -19587,7 +19713,7 @@ class LiveMarkdownService {
19587
19713
  payload,
19588
19714
  });
19589
19715
  if (payload) {
19590
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19716
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19591
19717
  this.getStorage.clear(key);
19592
19718
  }
19593
19719
  else {
@@ -19607,7 +19733,7 @@ class LiveMarkdownService {
19607
19733
  * @param backtest - Whether running in backtest mode
19608
19734
  * @returns Unique string key for memoization
19609
19735
  */
19610
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
19736
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
19611
19737
  const parts = [symbol, strategyName, exchangeName];
19612
19738
  if (frameName)
19613
19739
  parts.push(frameName);
@@ -19898,7 +20024,7 @@ class ScheduleMarkdownService {
19898
20024
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19899
20025
  * Each combination gets its own isolated storage instance.
19900
20026
  */
19901
- this.getStorage = functoolsKit.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));
20027
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
19902
20028
  /**
19903
20029
  * Subscribes to signal emitter to receive scheduled signal events.
19904
20030
  * Protected against multiple subscriptions.
@@ -20101,7 +20227,7 @@ class ScheduleMarkdownService {
20101
20227
  payload,
20102
20228
  });
20103
20229
  if (payload) {
20104
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20230
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20105
20231
  this.getStorage.clear(key);
20106
20232
  }
20107
20233
  else {
@@ -20121,7 +20247,7 @@ class ScheduleMarkdownService {
20121
20247
  * @param backtest - Whether running in backtest mode
20122
20248
  * @returns Unique string key for memoization
20123
20249
  */
20124
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
20250
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
20125
20251
  const parts = [symbol, strategyName, exchangeName];
20126
20252
  if (frameName)
20127
20253
  parts.push(frameName);
@@ -20369,7 +20495,7 @@ class PerformanceMarkdownService {
20369
20495
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
20370
20496
  * Each combination gets its own isolated storage instance.
20371
20497
  */
20372
- this.getStorage = functoolsKit.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));
20498
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
20373
20499
  /**
20374
20500
  * Subscribes to performance emitter to receive performance events.
20375
20501
  * Protected against multiple subscriptions.
@@ -20536,7 +20662,7 @@ class PerformanceMarkdownService {
20536
20662
  payload,
20537
20663
  });
20538
20664
  if (payload) {
20539
- const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20665
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20540
20666
  this.getStorage.clear(key);
20541
20667
  }
20542
20668
  else {
@@ -21006,7 +21132,7 @@ class WalkerMarkdownService {
21006
21132
  * @param backtest - Whether running in backtest mode
21007
21133
  * @returns Unique string key for memoization
21008
21134
  */
21009
- const CREATE_KEY_FN$b = (exchangeName, frameName, backtest) => {
21135
+ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21010
21136
  const parts = [exchangeName];
21011
21137
  if (frameName)
21012
21138
  parts.push(frameName);
@@ -21373,7 +21499,7 @@ class HeatMarkdownService {
21373
21499
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
21374
21500
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
21375
21501
  */
21376
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21502
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21377
21503
  /**
21378
21504
  * Subscribes to signal emitter to receive tick events.
21379
21505
  * Protected against multiple subscriptions.
@@ -21568,7 +21694,7 @@ class HeatMarkdownService {
21568
21694
  payload,
21569
21695
  });
21570
21696
  if (payload) {
21571
- const key = CREATE_KEY_FN$b(payload.exchangeName, payload.frameName, payload.backtest);
21697
+ const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
21572
21698
  this.getStorage.clear(key);
21573
21699
  }
21574
21700
  else {
@@ -22599,7 +22725,7 @@ class ClientPartial {
22599
22725
  * @param backtest - Whether running in backtest mode
22600
22726
  * @returns Unique string key for memoization
22601
22727
  */
22602
- const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22728
+ const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22603
22729
  /**
22604
22730
  * Creates a callback function for emitting profit events to partialProfitSubject.
22605
22731
  *
@@ -22721,7 +22847,7 @@ class PartialConnectionService {
22721
22847
  * Key format: "signalId:backtest" or "signalId:live"
22722
22848
  * Value: ClientPartial instance with logger and event emitters
22723
22849
  */
22724
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
22850
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
22725
22851
  return new ClientPartial({
22726
22852
  signalId,
22727
22853
  logger: this.loggerService,
@@ -22811,7 +22937,7 @@ class PartialConnectionService {
22811
22937
  const partial = this.getPartial(data.id, backtest);
22812
22938
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
22813
22939
  await partial.clear(symbol, data, priceClose, backtest);
22814
- const key = CREATE_KEY_FN$a(data.id, backtest);
22940
+ const key = CREATE_KEY_FN$c(data.id, backtest);
22815
22941
  this.getPartial.clear(key);
22816
22942
  };
22817
22943
  }
@@ -22827,7 +22953,7 @@ class PartialConnectionService {
22827
22953
  * @param backtest - Whether running in backtest mode
22828
22954
  * @returns Unique string key for memoization
22829
22955
  */
22830
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
22956
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
22831
22957
  const parts = [symbol, strategyName, exchangeName];
22832
22958
  if (frameName)
22833
22959
  parts.push(frameName);
@@ -23052,7 +23178,7 @@ class PartialMarkdownService {
23052
23178
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
23053
23179
  * Each combination gets its own isolated storage instance.
23054
23180
  */
23055
- this.getStorage = functoolsKit.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));
23181
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
23056
23182
  /**
23057
23183
  * Subscribes to partial profit/loss signal emitters to receive events.
23058
23184
  * Protected against multiple subscriptions.
@@ -23262,7 +23388,7 @@ class PartialMarkdownService {
23262
23388
  payload,
23263
23389
  });
23264
23390
  if (payload) {
23265
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23391
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23266
23392
  this.getStorage.clear(key);
23267
23393
  }
23268
23394
  else {
@@ -23278,7 +23404,7 @@ class PartialMarkdownService {
23278
23404
  * @param context - Context with strategyName, exchangeName, frameName
23279
23405
  * @returns Unique string key for memoization
23280
23406
  */
23281
- const CREATE_KEY_FN$8 = (context) => {
23407
+ const CREATE_KEY_FN$a = (context) => {
23282
23408
  const parts = [context.strategyName, context.exchangeName];
23283
23409
  if (context.frameName)
23284
23410
  parts.push(context.frameName);
@@ -23352,7 +23478,7 @@ class PartialGlobalService {
23352
23478
  * @param context - Context with strategyName, exchangeName and frameName
23353
23479
  * @param methodName - Name of the calling method for error tracking
23354
23480
  */
23355
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
23481
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
23356
23482
  this.loggerService.log("partialGlobalService validate", {
23357
23483
  context,
23358
23484
  methodName,
@@ -23807,7 +23933,7 @@ class ClientBreakeven {
23807
23933
  * @param backtest - Whether running in backtest mode
23808
23934
  * @returns Unique string key for memoization
23809
23935
  */
23810
- const CREATE_KEY_FN$7 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23936
+ const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23811
23937
  /**
23812
23938
  * Creates a callback function for emitting breakeven events to breakevenSubject.
23813
23939
  *
@@ -23893,7 +24019,7 @@ class BreakevenConnectionService {
23893
24019
  * Key format: "signalId:backtest" or "signalId:live"
23894
24020
  * Value: ClientBreakeven instance with logger and event emitter
23895
24021
  */
23896
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$7(signalId, backtest), (signalId, backtest) => {
24022
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
23897
24023
  return new ClientBreakeven({
23898
24024
  signalId,
23899
24025
  logger: this.loggerService,
@@ -23954,7 +24080,7 @@ class BreakevenConnectionService {
23954
24080
  const breakeven = this.getBreakeven(data.id, backtest);
23955
24081
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
23956
24082
  await breakeven.clear(symbol, data, priceClose, backtest);
23957
- const key = CREATE_KEY_FN$7(data.id, backtest);
24083
+ const key = CREATE_KEY_FN$9(data.id, backtest);
23958
24084
  this.getBreakeven.clear(key);
23959
24085
  };
23960
24086
  }
@@ -23970,7 +24096,7 @@ class BreakevenConnectionService {
23970
24096
  * @param backtest - Whether running in backtest mode
23971
24097
  * @returns Unique string key for memoization
23972
24098
  */
23973
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24099
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
23974
24100
  const parts = [symbol, strategyName, exchangeName];
23975
24101
  if (frameName)
23976
24102
  parts.push(frameName);
@@ -24147,7 +24273,7 @@ class BreakevenMarkdownService {
24147
24273
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24148
24274
  * Each combination gets its own isolated storage instance.
24149
24275
  */
24150
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
24276
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
24151
24277
  /**
24152
24278
  * Subscribes to breakeven signal emitter to receive events.
24153
24279
  * Protected against multiple subscriptions.
@@ -24336,7 +24462,7 @@ class BreakevenMarkdownService {
24336
24462
  payload,
24337
24463
  });
24338
24464
  if (payload) {
24339
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24465
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24340
24466
  this.getStorage.clear(key);
24341
24467
  }
24342
24468
  else {
@@ -24352,7 +24478,7 @@ class BreakevenMarkdownService {
24352
24478
  * @param context - Context with strategyName, exchangeName, frameName
24353
24479
  * @returns Unique string key for memoization
24354
24480
  */
24355
- const CREATE_KEY_FN$5 = (context) => {
24481
+ const CREATE_KEY_FN$7 = (context) => {
24356
24482
  const parts = [context.strategyName, context.exchangeName];
24357
24483
  if (context.frameName)
24358
24484
  parts.push(context.frameName);
@@ -24426,7 +24552,7 @@ class BreakevenGlobalService {
24426
24552
  * @param context - Context with strategyName, exchangeName and frameName
24427
24553
  * @param methodName - Name of the calling method for error tracking
24428
24554
  */
24429
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$5(context), (context, methodName) => {
24555
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
24430
24556
  this.loggerService.log("breakevenGlobalService validate", {
24431
24557
  context,
24432
24558
  methodName,
@@ -24646,7 +24772,7 @@ class ConfigValidationService {
24646
24772
  * @param backtest - Whether running in backtest mode
24647
24773
  * @returns Unique string key for memoization
24648
24774
  */
24649
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24775
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24650
24776
  const parts = [symbol, strategyName, exchangeName];
24651
24777
  if (frameName)
24652
24778
  parts.push(frameName);
@@ -24815,7 +24941,7 @@ class RiskMarkdownService {
24815
24941
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24816
24942
  * Each combination gets its own isolated storage instance.
24817
24943
  */
24818
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
24944
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
24819
24945
  /**
24820
24946
  * Subscribes to risk rejection emitter to receive rejection events.
24821
24947
  * Protected against multiple subscriptions.
@@ -25004,7 +25130,7 @@ class RiskMarkdownService {
25004
25130
  payload,
25005
25131
  });
25006
25132
  if (payload) {
25007
- const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25133
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25008
25134
  this.getStorage.clear(key);
25009
25135
  }
25010
25136
  else {
@@ -27692,7 +27818,7 @@ class SyncReportService {
27692
27818
  * @returns Colon-separated key string for memoization
27693
27819
  * @internal
27694
27820
  */
27695
- const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27821
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27696
27822
  const parts = [symbol, strategyName, exchangeName];
27697
27823
  if (frameName)
27698
27824
  parts.push(frameName);
@@ -27940,7 +28066,7 @@ class StrategyMarkdownService {
27940
28066
  *
27941
28067
  * @internal
27942
28068
  */
27943
- this.getStorage = functoolsKit.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));
28069
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
27944
28070
  /**
27945
28071
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
27946
28072
  *
@@ -28508,7 +28634,7 @@ class StrategyMarkdownService {
28508
28634
  this.clear = async (payload) => {
28509
28635
  this.loggerService.log("strategyMarkdownService clear", { payload });
28510
28636
  if (payload) {
28511
- const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28637
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28512
28638
  this.getStorage.clear(key);
28513
28639
  }
28514
28640
  else {
@@ -28616,7 +28742,7 @@ class StrategyMarkdownService {
28616
28742
  * Creates a unique key for memoizing ReportStorage instances.
28617
28743
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28618
28744
  */
28619
- const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28745
+ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28620
28746
  const parts = [symbol, strategyName, exchangeName];
28621
28747
  if (frameName)
28622
28748
  parts.push(frameName);
@@ -28748,7 +28874,7 @@ class ReportStorage {
28748
28874
  class SyncMarkdownService {
28749
28875
  constructor() {
28750
28876
  this.loggerService = inject(TYPES.loggerService);
28751
- this.getStorage = functoolsKit.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));
28877
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
28752
28878
  this.subscribe = functoolsKit.singleshot(() => {
28753
28879
  this.loggerService.log("syncMarkdownService init");
28754
28880
  const unsubscribe = syncSubject.subscribe(this.tick);
@@ -28823,7 +28949,7 @@ class SyncMarkdownService {
28823
28949
  this.clear = async (payload) => {
28824
28950
  this.loggerService.log("syncMarkdownService clear", { payload });
28825
28951
  if (payload) {
28826
- const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28952
+ const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28827
28953
  this.getStorage.clear(key);
28828
28954
  }
28829
28955
  else {
@@ -28833,6 +28959,275 @@ class SyncMarkdownService {
28833
28959
  }
28834
28960
  }
28835
28961
 
28962
+ const LISTEN_TIMEOUT$1 = 120000;
28963
+ /**
28964
+ * Creates a unique memoization key for a price stream.
28965
+ * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28966
+ *
28967
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
28968
+ * @param strategyName - Strategy identifier
28969
+ * @param exchangeName - Exchange identifier
28970
+ * @param frameName - Frame identifier (omitted when empty)
28971
+ * @param backtest - Whether running in backtest mode
28972
+ * @returns Unique string key for memoization
28973
+ */
28974
+ const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28975
+ const parts = [symbol, strategyName, exchangeName];
28976
+ if (frameName)
28977
+ parts.push(frameName);
28978
+ parts.push(backtest ? "backtest" : "live");
28979
+ return parts.join(":");
28980
+ };
28981
+ /**
28982
+ * Service for tracking the latest market price per symbol-strategy-exchange-frame combination.
28983
+ *
28984
+ * Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
28985
+ * by StrategyConnectionService. Consumers can synchronously read the last known price or
28986
+ * await the first value if none has arrived yet.
28987
+ *
28988
+ * Primary use case: providing the current price outside of a tick execution context,
28989
+ * e.g., when a command is triggered between ticks.
28990
+ *
28991
+ * Features:
28992
+ * - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
28993
+ * - Falls back to ExchangeConnectionService.getAveragePrice when called inside an execution context
28994
+ * - Waits up to LISTEN_TIMEOUT ms for the first price if none is cached yet
28995
+ * - clear() disposes the BehaviorSubject for a single key or all keys
28996
+ *
28997
+ * Architecture:
28998
+ * - Registered as singleton in DI container
28999
+ * - Updated by StrategyConnectionService after each tick
29000
+ * - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
29001
+ *
29002
+ * @example
29003
+ * ```typescript
29004
+ * const price = await backtest.priceMetaService.getCurrentPrice("BTCUSDT", context, false);
29005
+ * ```
29006
+ */
29007
+ class PriceMetaService {
29008
+ constructor() {
29009
+ this.loggerService = inject(TYPES.loggerService);
29010
+ this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
29011
+ /**
29012
+ * Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
29013
+ *
29014
+ * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
29015
+ * Instances are cached until clear() is called.
29016
+ */
29017
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$3(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
29018
+ /**
29019
+ * Returns the current market price for the given symbol and context.
29020
+ *
29021
+ * When called inside an execution context (i.e., during a signal handler or action),
29022
+ * delegates to ExchangeConnectionService.getAveragePrice for the live exchange price.
29023
+ * Otherwise, reads the last value from the cached BehaviorSubject. If no value has
29024
+ * been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
29025
+ *
29026
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29027
+ * @param context - Strategy, exchange, and frame identifiers
29028
+ * @param backtest - True if backtest mode, false if live mode
29029
+ * @returns Current market price in quote currency
29030
+ * @throws When no price arrives within LISTEN_TIMEOUT ms
29031
+ */
29032
+ this.getCurrentPrice = async (symbol, context, backtest) => {
29033
+ this.loggerService.log("priceMetaService getCurrentPrice", {
29034
+ symbol,
29035
+ context,
29036
+ backtest,
29037
+ });
29038
+ if (ExecutionContextService.hasContext() &&
29039
+ MethodContextService.hasContext()) {
29040
+ return await this.exchangeConnectionService.getAveragePrice(symbol);
29041
+ }
29042
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29043
+ if (source.data) {
29044
+ return source.data;
29045
+ }
29046
+ console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$3(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
29047
+ const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
29048
+ if (typeof currentPrice === "symbol") {
29049
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$3(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
29050
+ }
29051
+ return currentPrice;
29052
+ };
29053
+ /**
29054
+ * Pushes a new price value into the BehaviorSubject for the given key.
29055
+ *
29056
+ * Called by StrategyConnectionService after each strategy tick to keep
29057
+ * the cached price up to date.
29058
+ *
29059
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29060
+ * @param currentPrice - The latest price from the tick
29061
+ * @param context - Strategy, exchange, and frame identifiers
29062
+ * @param backtest - True if backtest mode, false if live mode
29063
+ */
29064
+ this.next = async (symbol, currentPrice, context, backtest) => {
29065
+ this.loggerService.log("priceMetaService next", {
29066
+ symbol,
29067
+ currentPrice,
29068
+ context,
29069
+ backtest,
29070
+ });
29071
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29072
+ source.next(currentPrice);
29073
+ };
29074
+ /**
29075
+ * Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
29076
+ *
29077
+ * When called without arguments, clears all memoized price streams.
29078
+ * When called with a payload, clears only the stream for the specified key.
29079
+ * Should be called at strategy start (Backtest/Live/Walker) to reset state.
29080
+ *
29081
+ * @param payload - Optional key to clear a single stream; omit to clear all
29082
+ */
29083
+ this.clear = (payload) => {
29084
+ this.loggerService.log("priceMetaService clear", {
29085
+ payload
29086
+ });
29087
+ if (!payload) {
29088
+ this.getSource.clear();
29089
+ return;
29090
+ }
29091
+ const key = CREATE_KEY_FN$3(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29092
+ this.getSource.clear(key);
29093
+ };
29094
+ }
29095
+ }
29096
+
29097
+ const LISTEN_TIMEOUT = 120000;
29098
+ /**
29099
+ * Creates a unique memoization key for a timestamp stream.
29100
+ * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
29101
+ *
29102
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29103
+ * @param strategyName - Strategy identifier
29104
+ * @param exchangeName - Exchange identifier
29105
+ * @param frameName - Frame identifier (omitted when empty)
29106
+ * @param backtest - Whether running in backtest mode
29107
+ * @returns Unique string key for memoization
29108
+ */
29109
+ const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29110
+ const parts = [symbol, strategyName, exchangeName];
29111
+ if (frameName)
29112
+ parts.push(frameName);
29113
+ parts.push(backtest ? "backtest" : "live");
29114
+ return parts.join(":");
29115
+ };
29116
+ /**
29117
+ * Service for tracking the latest candle timestamp per symbol-strategy-exchange-frame combination.
29118
+ *
29119
+ * Maintains a memoized BehaviorSubject per unique key that is updated on every strategy tick
29120
+ * by StrategyConnectionService. Consumers can synchronously read the last known timestamp or
29121
+ * await the first value if none has arrived yet.
29122
+ *
29123
+ * Primary use case: providing the current candle time outside of a tick execution context,
29124
+ * e.g., when a command is triggered between ticks.
29125
+ *
29126
+ * Features:
29127
+ * - One BehaviorSubject per (symbol, strategyName, exchangeName, frameName, backtest) key
29128
+ * - Falls back to ExecutionContextService.context.when when called inside an execution context
29129
+ * - Waits up to LISTEN_TIMEOUT ms for the first timestamp if none is cached yet
29130
+ * - clear() disposes the BehaviorSubject for a single key or all keys
29131
+ *
29132
+ * Architecture:
29133
+ * - Registered as singleton in DI container
29134
+ * - Updated by StrategyConnectionService after each tick
29135
+ * - Cleared by Backtest/Live/Walker at strategy start to prevent stale data
29136
+ *
29137
+ * @example
29138
+ * ```typescript
29139
+ * const ts = await backtest.timeMetaService.getTimestamp("BTCUSDT", context, false);
29140
+ * ```
29141
+ */
29142
+ class TimeMetaService {
29143
+ constructor() {
29144
+ this.loggerService = inject(TYPES.loggerService);
29145
+ this.executionContextService = inject(TYPES.executionContextService);
29146
+ /**
29147
+ * Memoized factory for BehaviorSubject streams keyed by (symbol, strategyName, exchangeName, frameName, backtest).
29148
+ *
29149
+ * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
29150
+ * Instances are cached until clear() is called.
29151
+ */
29152
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$2(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
29153
+ /**
29154
+ * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
29155
+ *
29156
+ * When called inside an execution context (i.e., during a signal handler or action),
29157
+ * reads the timestamp directly from ExecutionContextService.context.when.
29158
+ * Otherwise, reads the last value from the cached BehaviorSubject. If no value has
29159
+ * been emitted yet, waits up to LISTEN_TIMEOUT ms for the first tick before throwing.
29160
+ *
29161
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29162
+ * @param context - Strategy, exchange, and frame identifiers
29163
+ * @param backtest - True if backtest mode, false if live mode
29164
+ * @returns Unix timestamp in milliseconds of the latest processed candle
29165
+ * @throws When no timestamp arrives within LISTEN_TIMEOUT ms
29166
+ */
29167
+ this.getTimestamp = async (symbol, context, backtest) => {
29168
+ this.loggerService.log("timeMetaService getTimestamp", {
29169
+ symbol,
29170
+ context,
29171
+ backtest,
29172
+ });
29173
+ if (ExecutionContextService.hasContext()) {
29174
+ return this.executionContextService.context.when.getTime();
29175
+ }
29176
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29177
+ if (source.data) {
29178
+ return source.data;
29179
+ }
29180
+ console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$2(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
29181
+ const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
29182
+ if (typeof timestamp === "symbol") {
29183
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$2(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
29184
+ }
29185
+ return timestamp;
29186
+ };
29187
+ /**
29188
+ * Pushes a new timestamp value into the BehaviorSubject for the given key.
29189
+ *
29190
+ * Called by StrategyConnectionService after each strategy tick to keep
29191
+ * the cached timestamp up to date.
29192
+ *
29193
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
29194
+ * @param timestamp - The createdAt timestamp from the tick (milliseconds)
29195
+ * @param context - Strategy, exchange, and frame identifiers
29196
+ * @param backtest - True if backtest mode, false if live mode
29197
+ */
29198
+ this.next = async (symbol, timestamp, context, backtest) => {
29199
+ this.loggerService.log("timeMetaService next", {
29200
+ symbol,
29201
+ timestamp,
29202
+ context,
29203
+ backtest,
29204
+ });
29205
+ const source = this.getSource(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
29206
+ source.next(timestamp);
29207
+ };
29208
+ /**
29209
+ * Disposes cached BehaviorSubject(s) to free memory and prevent stale data.
29210
+ *
29211
+ * When called without arguments, clears all memoized timestamp streams.
29212
+ * When called with a payload, clears only the stream for the specified key.
29213
+ * Should be called at strategy start (Backtest/Live/Walker) to reset state.
29214
+ *
29215
+ * @param payload - Optional key to clear a single stream; omit to clear all
29216
+ */
29217
+ this.clear = (payload) => {
29218
+ this.loggerService.log("timeMetaService clear", {
29219
+ payload,
29220
+ });
29221
+ if (!payload) {
29222
+ this.getSource.clear();
29223
+ return;
29224
+ }
29225
+ const key = CREATE_KEY_FN$2(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29226
+ this.getSource.clear(key);
29227
+ };
29228
+ }
29229
+ }
29230
+
28836
29231
  {
28837
29232
  provide(TYPES.loggerService, () => new LoggerService());
28838
29233
  }
@@ -28865,6 +29260,10 @@ class SyncMarkdownService {
28865
29260
  provide(TYPES.actionCoreService, () => new ActionCoreService());
28866
29261
  provide(TYPES.frameCoreService, () => new FrameCoreService());
28867
29262
  }
29263
+ {
29264
+ provide(TYPES.priceMetaService, () => new PriceMetaService());
29265
+ provide(TYPES.timeMetaService, () => new TimeMetaService());
29266
+ }
28868
29267
  {
28869
29268
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
28870
29269
  provide(TYPES.riskGlobalService, () => new RiskGlobalService());
@@ -28956,6 +29355,10 @@ const coreServices = {
28956
29355
  actionCoreService: inject(TYPES.actionCoreService),
28957
29356
  frameCoreService: inject(TYPES.frameCoreService),
28958
29357
  };
29358
+ const metaServices = {
29359
+ timeMetaService: inject(TYPES.timeMetaService),
29360
+ priceMetaService: inject(TYPES.priceMetaService),
29361
+ };
28959
29362
  const globalServices = {
28960
29363
  sizingGlobalService: inject(TYPES.sizingGlobalService),
28961
29364
  riskGlobalService: inject(TYPES.riskGlobalService),
@@ -29020,6 +29423,7 @@ const backtest = {
29020
29423
  ...connectionServices,
29021
29424
  ...schemaServices,
29022
29425
  ...coreServices,
29426
+ ...metaServices,
29023
29427
  ...globalServices,
29024
29428
  ...commandServices,
29025
29429
  ...logicPrivateServices,
@@ -34506,6 +34910,20 @@ class BacktestInstance {
34506
34910
  frameName: context.frameName,
34507
34911
  backtest: true,
34508
34912
  });
34913
+ bt.timeMetaService.clear({
34914
+ symbol,
34915
+ strategyName: context.strategyName,
34916
+ exchangeName: context.exchangeName,
34917
+ frameName: context.frameName,
34918
+ backtest: true,
34919
+ });
34920
+ bt.priceMetaService.clear({
34921
+ symbol,
34922
+ strategyName: context.strategyName,
34923
+ exchangeName: context.exchangeName,
34924
+ frameName: context.frameName,
34925
+ backtest: true,
34926
+ });
34509
34927
  }
34510
34928
  {
34511
34929
  const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
@@ -36410,6 +36828,20 @@ class LiveInstance {
36410
36828
  frameName: "",
36411
36829
  backtest: false,
36412
36830
  });
36831
+ bt.timeMetaService.clear({
36832
+ symbol,
36833
+ strategyName: context.strategyName,
36834
+ exchangeName: context.exchangeName,
36835
+ frameName: "",
36836
+ backtest: false,
36837
+ });
36838
+ bt.priceMetaService.clear({
36839
+ symbol,
36840
+ strategyName: context.strategyName,
36841
+ exchangeName: context.exchangeName,
36842
+ frameName: "",
36843
+ backtest: false,
36844
+ });
36413
36845
  }
36414
36846
  {
36415
36847
  const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
@@ -40558,6 +40990,20 @@ class WalkerInstance {
40558
40990
  frameName: walkerSchema.frameName,
40559
40991
  backtest: true,
40560
40992
  });
40993
+ bt.timeMetaService.clear({
40994
+ symbol,
40995
+ strategyName,
40996
+ exchangeName: walkerSchema.exchangeName,
40997
+ frameName: walkerSchema.frameName,
40998
+ backtest: true,
40999
+ });
41000
+ bt.priceMetaService.clear({
41001
+ symbol,
41002
+ strategyName,
41003
+ exchangeName: walkerSchema.exchangeName,
41004
+ frameName: walkerSchema.frameName,
41005
+ backtest: true,
41006
+ });
40561
41007
  }
40562
41008
  {
40563
41009
  const { riskName, riskList, actions } = bt.strategySchemaService.get(strategyName);
@@ -43525,13 +43971,15 @@ class NotificationMemoryBacktestUtils {
43525
43971
  * Handles signal sync events (signal-open, signal-close).
43526
43972
  * @param data - The signal sync contract data
43527
43973
  */
43528
- this.handleSync = async (data) => {
43974
+ this.handleSync = functoolsKit.trycatch(async (data) => {
43529
43975
  bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
43530
43976
  signalId: data.signalId,
43531
43977
  action: data.action,
43532
43978
  });
43533
43979
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43534
- };
43980
+ }, {
43981
+ defaultValue: null,
43982
+ });
43535
43983
  /**
43536
43984
  * Handles risk rejection event.
43537
43985
  * @param data - The risk contract data
@@ -43640,8 +44088,10 @@ class NotificationDummyBacktestUtils {
43640
44088
  /**
43641
44089
  * No-op handler for signal sync event.
43642
44090
  */
43643
- this.handleSync = async () => {
43644
- };
44091
+ this.handleSync = functoolsKit.trycatch(async () => {
44092
+ }, {
44093
+ defaultValue: null,
44094
+ });
43645
44095
  /**
43646
44096
  * No-op handler for risk rejection event.
43647
44097
  */
@@ -43780,7 +44230,7 @@ class NotificationPersistBacktestUtils {
43780
44230
  * Handles signal sync events (signal-open, signal-close).
43781
44231
  * @param data - The signal sync contract data
43782
44232
  */
43783
- this.handleSync = async (data) => {
44233
+ this.handleSync = functoolsKit.trycatch(async (data) => {
43784
44234
  bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
43785
44235
  signalId: data.signalId,
43786
44236
  action: data.action,
@@ -43788,7 +44238,9 @@ class NotificationPersistBacktestUtils {
43788
44238
  await this.waitForInit();
43789
44239
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43790
44240
  await this._updateNotifications();
43791
- };
44241
+ }, {
44242
+ defaultValue: null,
44243
+ });
43792
44244
  /**
43793
44245
  * Handles risk rejection event.
43794
44246
  * @param data - The risk contract data
@@ -43971,13 +44423,15 @@ class NotificationMemoryLiveUtils {
43971
44423
  * Handles signal sync events (signal-open, signal-close).
43972
44424
  * @param data - The signal sync contract data
43973
44425
  */
43974
- this.handleSync = async (data) => {
44426
+ this.handleSync = functoolsKit.trycatch(async (data) => {
43975
44427
  bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SYNC, {
43976
44428
  signalId: data.signalId,
43977
44429
  action: data.action,
43978
44430
  });
43979
44431
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43980
- };
44432
+ }, {
44433
+ defaultValue: null,
44434
+ });
43981
44435
  /**
43982
44436
  * Handles risk rejection event.
43983
44437
  * @param data - The risk contract data
@@ -44086,8 +44540,10 @@ class NotificationDummyLiveUtils {
44086
44540
  /**
44087
44541
  * No-op handler for signal sync event.
44088
44542
  */
44089
- this.handleSync = async () => {
44090
- };
44543
+ this.handleSync = functoolsKit.trycatch(async () => {
44544
+ }, {
44545
+ defaultValue: null,
44546
+ });
44091
44547
  /**
44092
44548
  * No-op handler for risk rejection event.
44093
44549
  */
@@ -44227,7 +44683,7 @@ class NotificationPersistLiveUtils {
44227
44683
  * Handles signal sync events (signal-open, signal-close).
44228
44684
  * @param data - The signal sync contract data
44229
44685
  */
44230
- this.handleSync = async (data) => {
44686
+ this.handleSync = functoolsKit.trycatch(async (data) => {
44231
44687
  bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SYNC, {
44232
44688
  signalId: data.signalId,
44233
44689
  action: data.action,
@@ -44235,7 +44691,9 @@ class NotificationPersistLiveUtils {
44235
44691
  await this.waitForInit();
44236
44692
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
44237
44693
  await this._updateNotifications();
44238
- };
44694
+ }, {
44695
+ defaultValue: null,
44696
+ });
44239
44697
  /**
44240
44698
  * Handles risk rejection event.
44241
44699
  * @param data - The risk contract data
@@ -44397,9 +44855,11 @@ class NotificationBacktestAdapter {
44397
44855
  * Proxies call to the underlying notification adapter.
44398
44856
  * @param data - The signal sync contract data
44399
44857
  */
44400
- this.handleSync = async (data) => {
44858
+ this.handleSync = functoolsKit.trycatch(async (data) => {
44401
44859
  return await this._notificationBacktestUtils.handleSync(data);
44402
- };
44860
+ }, {
44861
+ defaultValue: null,
44862
+ });
44403
44863
  /**
44404
44864
  * Handles risk rejection event.
44405
44865
  * Proxies call to the underlying notification adapter.
@@ -44541,9 +45001,11 @@ class NotificationLiveAdapter {
44541
45001
  * Proxies call to the underlying notification adapter.
44542
45002
  * @param data - The signal sync contract data
44543
45003
  */
44544
- this.handleSync = async (data) => {
45004
+ this.handleSync = functoolsKit.trycatch(async (data) => {
44545
45005
  return await this._notificationLiveUtils.handleSync(data);
44546
- };
45006
+ }, {
45007
+ defaultValue: null,
45008
+ });
44547
45009
  /**
44548
45010
  * Handles risk rejection event.
44549
45011
  * Proxies call to the underlying notification adapter.