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.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,
@@ -3538,19 +3543,6 @@ const beginTime = (run) => (...args) => {
3538
3543
  return fn();
3539
3544
  };
3540
3545
 
3541
- /**
3542
- * Retrieves the current timestamp for debugging purposes.
3543
- * 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.
3544
- * Can be empty (undefined) if not called from strategy async context, as it's intended for debugging and not critical for logic.
3545
- * @return {number | undefined} The current timestamp in milliseconds from the execution context, or undefined if not available.
3546
- */
3547
- const getDebugTimestamp = () => {
3548
- if (ExecutionContextService.hasContext()) {
3549
- return bt.executionContextService.context.when.getTime();
3550
- }
3551
- return undefined;
3552
- };
3553
-
3554
3546
  const INTERVAL_MINUTES$6 = {
3555
3547
  "1m": 1,
3556
3548
  "3m": 3,
@@ -4258,7 +4250,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4258
4250
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
4259
4251
  timestamp: currentTime,
4260
4252
  _isScheduled: false,
4261
- _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 }],
4262
4254
  };
4263
4255
  // Валидируем сигнал перед возвратом
4264
4256
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4282,7 +4274,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4282
4274
  pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
4283
4275
  timestamp: currentTime,
4284
4276
  _isScheduled: true,
4285
- _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 }],
4286
4278
  };
4287
4279
  // Валидируем сигнал перед возвратом
4288
4280
  VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
@@ -4302,7 +4294,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4302
4294
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
4303
4295
  timestamp: currentTime,
4304
4296
  _isScheduled: false,
4305
- _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 }],
4306
4298
  };
4307
4299
  // Валидируем сигнал перед возвратом
4308
4300
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4372,7 +4364,7 @@ const WAIT_FOR_DISPOSE_FN$1 = async (self) => {
4372
4364
  self.params.logger.debug("ClientStrategy dispose");
4373
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);
4374
4366
  };
4375
- const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4367
+ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
4376
4368
  // Initialize partial array if not present
4377
4369
  if (!signal._partial)
4378
4370
  signal._partial = [];
@@ -4401,7 +4393,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4401
4393
  entryCountAtClose,
4402
4394
  currentPrice,
4403
4395
  costBasisAtClose: remainingCostBasis,
4404
- debugTimestamp: getDebugTimestamp(),
4396
+ timestamp,
4405
4397
  });
4406
4398
  self.params.logger.info("PARTIAL_PROFIT_FN executed", {
4407
4399
  signalId: signal.id,
@@ -4411,7 +4403,7 @@ const PARTIAL_PROFIT_FN = (self, signal, percentToClose, currentPrice) => {
4411
4403
  });
4412
4404
  return true;
4413
4405
  };
4414
- const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
4406
+ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice, timestamp) => {
4415
4407
  // Initialize partial array if not present
4416
4408
  if (!signal._partial)
4417
4409
  signal._partial = [];
@@ -4439,7 +4431,7 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
4439
4431
  currentPrice,
4440
4432
  entryCountAtClose,
4441
4433
  costBasisAtClose: remainingCostBasis,
4442
- debugTimestamp: getDebugTimestamp(),
4434
+ timestamp,
4443
4435
  });
4444
4436
  self.params.logger.warn("PARTIAL_LOSS_FN executed", {
4445
4437
  signalId: signal.id,
@@ -4836,10 +4828,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4836
4828
  });
4837
4829
  return true;
4838
4830
  };
4839
- 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) => {
4840
4832
  // Ensure _entry is initialized (handles signals loaded from disk without _entry)
4841
4833
  if (!signal._entry || signal._entry.length === 0) {
4842
- 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 }];
4843
4835
  }
4844
4836
  if (signal.position === "long") {
4845
4837
  // LONG: new entry must beat the all-time low — strictly below every prior entry price
@@ -4869,7 +4861,7 @@ const AVERAGE_BUY_FN = (self, signal, currentPrice, cost = GLOBAL_CONFIG.CC_POSI
4869
4861
  return false;
4870
4862
  }
4871
4863
  }
4872
- signal._entry.push({ price: currentPrice, cost, debugTimestamp: getDebugTimestamp() });
4864
+ signal._entry.push({ price: currentPrice, cost, timestamp });
4873
4865
  self.params.logger.info("AVERAGE_BUY_FN executed", {
4874
4866
  signalId: signal.id,
4875
4867
  position: signal.position,
@@ -6654,16 +6646,16 @@ class ClientStrategy {
6654
6646
  * // No DCA: [{ price: 43000, cost: 100 }]
6655
6647
  * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
6656
6648
  */
6657
- async getPositionEntries(symbol) {
6649
+ async getPositionEntries(symbol, timestamp) {
6658
6650
  this.params.logger.debug("ClientStrategy getPositionEntries", { symbol });
6659
6651
  if (!this._pendingSignal) {
6660
6652
  return null;
6661
6653
  }
6662
6654
  const entries = this._pendingSignal._entry;
6663
6655
  if (!entries || entries.length === 0) {
6664
- 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 }];
6665
6657
  }
6666
- return entries.map(({ price, cost }) => ({ price, cost }));
6658
+ return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
6667
6659
  }
6668
6660
  /**
6669
6661
  * Performs a single tick of strategy execution.
@@ -7499,7 +7491,7 @@ class ClientStrategy {
7499
7491
  * // success3 = false (skipped, would exceed 100%)
7500
7492
  * ```
7501
7493
  */
7502
- async partialProfit(symbol, percentToClose, currentPrice, backtest) {
7494
+ async partialProfit(symbol, percentToClose, currentPrice, backtest, timestamp) {
7503
7495
  this.params.logger.debug("ClientStrategy partialProfit", {
7504
7496
  symbol,
7505
7497
  percentToClose,
@@ -7563,7 +7555,7 @@ class ClientStrategy {
7563
7555
  return false;
7564
7556
  }
7565
7557
  // Execute partial close logic
7566
- const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice);
7558
+ const wasExecuted = PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
7567
7559
  // If partial was not executed (exceeded 100%), return false without persistence
7568
7560
  if (!wasExecuted) {
7569
7561
  return false;
@@ -7682,7 +7674,7 @@ class ClientStrategy {
7682
7674
  * // success3 = false (skipped, would exceed 100%)
7683
7675
  * ```
7684
7676
  */
7685
- async partialLoss(symbol, percentToClose, currentPrice, backtest) {
7677
+ async partialLoss(symbol, percentToClose, currentPrice, backtest, timestamp) {
7686
7678
  this.params.logger.debug("ClientStrategy partialLoss", {
7687
7679
  symbol,
7688
7680
  percentToClose,
@@ -7746,7 +7738,7 @@ class ClientStrategy {
7746
7738
  return false;
7747
7739
  }
7748
7740
  // Execute partial close logic
7749
- const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice);
7741
+ const wasExecuted = PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice, timestamp);
7750
7742
  // If partial was not executed (exceeded 100%), return false without persistence
7751
7743
  if (!wasExecuted) {
7752
7744
  return false;
@@ -8502,7 +8494,7 @@ class ClientStrategy {
8502
8494
  * @param backtest - Whether running in backtest mode
8503
8495
  * @returns Promise<boolean> - true if entry added, false if rejected by direction check
8504
8496
  */
8505
- 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) {
8506
8498
  this.params.logger.debug("ClientStrategy averageBuy", {
8507
8499
  symbol,
8508
8500
  currentPrice,
@@ -8517,7 +8509,7 @@ class ClientStrategy {
8517
8509
  throw new Error(`ClientStrategy averageBuy: currentPrice must be a positive finite number, got ${currentPrice}`);
8518
8510
  }
8519
8511
  // Execute averaging logic
8520
- const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, cost);
8512
+ const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice, timestamp, cost);
8521
8513
  if (!result) {
8522
8514
  return false;
8523
8515
  }
@@ -8981,7 +8973,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
8981
8973
  * @param backtest - Whether running in backtest mode
8982
8974
  * @returns Unique string key for memoization
8983
8975
  */
8984
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
8976
+ const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
8985
8977
  const parts = [symbol, strategyName, exchangeName];
8986
8978
  if (frameName)
8987
8979
  parts.push(frameName);
@@ -9158,6 +9150,8 @@ class StrategyConnectionService {
9158
9150
  this.partialConnectionService = inject(TYPES.partialConnectionService);
9159
9151
  this.breakevenConnectionService = inject(TYPES.breakevenConnectionService);
9160
9152
  this.actionCoreService = inject(TYPES.actionCoreService);
9153
+ this.timeMetaService = inject(TYPES.timeMetaService);
9154
+ this.priceMetaService = inject(TYPES.priceMetaService);
9161
9155
  /**
9162
9156
  * Retrieves memoized ClientStrategy instance for given symbol-strategy pair with exchange and frame isolation.
9163
9157
  *
@@ -9171,7 +9165,7 @@ class StrategyConnectionService {
9171
9165
  * @param backtest - Whether running in backtest mode
9172
9166
  * @returns Configured ClientStrategy instance
9173
9167
  */
9174
- 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) => {
9175
9169
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
9176
9170
  return new ClientStrategy({
9177
9171
  symbol,
@@ -9258,6 +9252,20 @@ class StrategyConnectionService {
9258
9252
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9259
9253
  return await strategy.getTotalCostClosed(symbol);
9260
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
+ */
9261
9269
  this.getPositionAveragePrice = async (backtest, symbol, context) => {
9262
9270
  this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
9263
9271
  symbol,
@@ -9267,6 +9275,19 @@ class StrategyConnectionService {
9267
9275
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9268
9276
  return await strategy.getPositionAveragePrice(symbol);
9269
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
+ */
9270
9291
  this.getPositionInvestedCount = async (backtest, symbol, context) => {
9271
9292
  this.loggerService.log("strategyConnectionService getPositionInvestedCount", {
9272
9293
  symbol,
@@ -9276,6 +9297,19 @@ class StrategyConnectionService {
9276
9297
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9277
9298
  return await strategy.getPositionInvestedCount(symbol);
9278
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
+ */
9279
9313
  this.getPositionInvestedCost = async (backtest, symbol, context) => {
9280
9314
  this.loggerService.log("strategyConnectionService getPositionInvestedCost", {
9281
9315
  symbol,
@@ -9285,6 +9319,20 @@ class StrategyConnectionService {
9285
9319
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9286
9320
  return await strategy.getPositionInvestedCost(symbol);
9287
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
+ */
9288
9336
  this.getPositionPnlPercent = async (backtest, symbol, currentPrice, context) => {
9289
9337
  this.loggerService.log("strategyConnectionService getPositionPnlPercent", {
9290
9338
  symbol,
@@ -9295,6 +9343,20 @@ class StrategyConnectionService {
9295
9343
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9296
9344
  return await strategy.getPositionPnlPercent(symbol, currentPrice);
9297
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
+ */
9298
9360
  this.getPositionPnlCost = async (backtest, symbol, currentPrice, context) => {
9299
9361
  this.loggerService.log("strategyConnectionService getPositionPnlCost", {
9300
9362
  symbol,
@@ -9305,6 +9367,27 @@ class StrategyConnectionService {
9305
9367
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9306
9368
  return await strategy.getPositionPnlCost(symbol, currentPrice);
9307
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
+ */
9308
9391
  this.getPositionLevels = async (backtest, symbol, context) => {
9309
9392
  this.loggerService.log("strategyConnectionService getPositionLevels", {
9310
9393
  symbol,
@@ -9314,6 +9397,20 @@ class StrategyConnectionService {
9314
9397
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9315
9398
  return await strategy.getPositionLevels(symbol);
9316
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
+ */
9317
9414
  this.getPositionPartials = async (backtest, symbol, context) => {
9318
9415
  this.loggerService.log("strategyConnectionService getPositionPartials", {
9319
9416
  symbol,
@@ -9323,6 +9420,27 @@ class StrategyConnectionService {
9323
9420
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9324
9421
  return await strategy.getPositionPartials(symbol);
9325
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
+ */
9326
9444
  this.getPositionEntries = async (backtest, symbol, context) => {
9327
9445
  this.loggerService.log("strategyConnectionService getPositionEntries", {
9328
9446
  symbol,
@@ -9330,7 +9448,8 @@ class StrategyConnectionService {
9330
9448
  backtest,
9331
9449
  });
9332
9450
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9333
- return await strategy.getPositionEntries(symbol);
9451
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9452
+ return await strategy.getPositionEntries(symbol, timestamp);
9334
9453
  };
9335
9454
  /**
9336
9455
  * Retrieves the currently active scheduled signal for the strategy.
@@ -9432,6 +9551,10 @@ class StrategyConnectionService {
9432
9551
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9433
9552
  await strategy.waitForInit();
9434
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
+ }
9435
9558
  {
9436
9559
  await CALL_SIGNAL_EMIT_FN(this, tick, context, backtest, symbol);
9437
9560
  }
@@ -9538,7 +9661,7 @@ class StrategyConnectionService {
9538
9661
  }
9539
9662
  return;
9540
9663
  }
9541
- 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);
9542
9665
  if (!this.getStrategy.has(key)) {
9543
9666
  return;
9544
9667
  }
@@ -9607,9 +9730,9 @@ class StrategyConnectionService {
9607
9730
  * @param context - Execution context with strategyName, exchangeName, frameName
9608
9731
  * @returns Promise<boolean> - true if `partialProfit` would execute, false otherwise
9609
9732
  */
9610
- this.validatePartialProfit = (backtest, symbol, percentToClose, currentPrice, context) => {
9733
+ this.validatePartialProfit = async (backtest, symbol, percentToClose, currentPrice, context) => {
9611
9734
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9612
- return Promise.resolve(strategy.validatePartialProfit(symbol, percentToClose, currentPrice));
9735
+ return await strategy.validatePartialProfit(symbol, percentToClose, currentPrice);
9613
9736
  };
9614
9737
  /**
9615
9738
  * Executes partial close at profit level (moving toward TP).
@@ -9650,7 +9773,8 @@ class StrategyConnectionService {
9650
9773
  backtest,
9651
9774
  });
9652
9775
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9653
- 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);
9654
9778
  };
9655
9779
  /**
9656
9780
  * Checks whether `partialLoss` would succeed without executing it.
@@ -9663,9 +9787,9 @@ class StrategyConnectionService {
9663
9787
  * @param context - Execution context with strategyName, exchangeName, frameName
9664
9788
  * @returns Promise<boolean> - true if `partialLoss` would execute, false otherwise
9665
9789
  */
9666
- this.validatePartialLoss = (backtest, symbol, percentToClose, currentPrice, context) => {
9790
+ this.validatePartialLoss = async (backtest, symbol, percentToClose, currentPrice, context) => {
9667
9791
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9668
- return Promise.resolve(strategy.validatePartialLoss(symbol, percentToClose, currentPrice));
9792
+ return await strategy.validatePartialLoss(symbol, percentToClose, currentPrice);
9669
9793
  };
9670
9794
  /**
9671
9795
  * Executes partial close at loss level (moving toward SL).
@@ -9706,7 +9830,8 @@ class StrategyConnectionService {
9706
9830
  backtest,
9707
9831
  });
9708
9832
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9709
- 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);
9710
9835
  };
9711
9836
  /**
9712
9837
  * Checks whether `trailingStop` would succeed without executing it.
@@ -9719,9 +9844,9 @@ class StrategyConnectionService {
9719
9844
  * @param context - Execution context with strategyName, exchangeName, frameName
9720
9845
  * @returns Promise<boolean> - true if `trailingStop` would execute, false otherwise
9721
9846
  */
9722
- this.validateTrailingStop = (backtest, symbol, percentShift, currentPrice, context) => {
9847
+ this.validateTrailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
9723
9848
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9724
- return Promise.resolve(strategy.validateTrailingStop(symbol, percentShift, currentPrice));
9849
+ return await strategy.validateTrailingStop(symbol, percentShift, currentPrice);
9725
9850
  };
9726
9851
  /**
9727
9852
  * Adjusts the trailing stop-loss distance for an active pending signal.
@@ -9773,9 +9898,9 @@ class StrategyConnectionService {
9773
9898
  * @param context - Execution context with strategyName, exchangeName, frameName
9774
9899
  * @returns Promise<boolean> - true if `trailingTake` would execute, false otherwise
9775
9900
  */
9776
- this.validateTrailingTake = (backtest, symbol, percentShift, currentPrice, context) => {
9901
+ this.validateTrailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
9777
9902
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9778
- return Promise.resolve(strategy.validateTrailingTake(symbol, percentShift, currentPrice));
9903
+ return await strategy.validateTrailingTake(symbol, percentShift, currentPrice);
9779
9904
  };
9780
9905
  /**
9781
9906
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -9826,9 +9951,9 @@ class StrategyConnectionService {
9826
9951
  * @param context - Execution context with strategyName, exchangeName, frameName
9827
9952
  * @returns Promise<boolean> - true if `breakeven` would execute, false otherwise
9828
9953
  */
9829
- this.validateBreakeven = (backtest, symbol, currentPrice, context) => {
9954
+ this.validateBreakeven = async (backtest, symbol, currentPrice, context) => {
9830
9955
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9831
- return Promise.resolve(strategy.validateBreakeven(symbol, currentPrice));
9956
+ return await strategy.validateBreakeven(symbol, currentPrice);
9832
9957
  };
9833
9958
  /**
9834
9959
  * Delegates to ClientStrategy.breakeven() with current execution context.
@@ -9905,9 +10030,9 @@ class StrategyConnectionService {
9905
10030
  * @param context - Execution context with strategyName, exchangeName, frameName
9906
10031
  * @returns Promise<boolean> - true if `averageBuy` would execute, false otherwise
9907
10032
  */
9908
- this.validateAverageBuy = (backtest, symbol, currentPrice, context) => {
10033
+ this.validateAverageBuy = async (backtest, symbol, currentPrice, context) => {
9909
10034
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9910
- return Promise.resolve(strategy.validateAverageBuy(symbol, currentPrice));
10035
+ return await strategy.validateAverageBuy(symbol, currentPrice);
9911
10036
  };
9912
10037
  /**
9913
10038
  * Adds a new DCA entry to the active pending signal.
@@ -9928,7 +10053,8 @@ class StrategyConnectionService {
9928
10053
  backtest,
9929
10054
  });
9930
10055
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9931
- 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);
9932
10058
  };
9933
10059
  }
9934
10060
  }
@@ -10669,7 +10795,7 @@ class ClientRisk {
10669
10795
  * @param backtest - Whether running in backtest mode
10670
10796
  * @returns Unique string key for memoization
10671
10797
  */
10672
- const CREATE_KEY_FN$l = (riskName, exchangeName, frameName, backtest) => {
10798
+ const CREATE_KEY_FN$n = (riskName, exchangeName, frameName, backtest) => {
10673
10799
  const parts = [riskName, exchangeName];
10674
10800
  if (frameName)
10675
10801
  parts.push(frameName);
@@ -10768,7 +10894,7 @@ class RiskConnectionService {
10768
10894
  * @param backtest - True if backtest mode, false if live mode
10769
10895
  * @returns Configured ClientRisk instance
10770
10896
  */
10771
- 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) => {
10772
10898
  const schema = this.riskSchemaService.get(riskName);
10773
10899
  return new ClientRisk({
10774
10900
  ...schema,
@@ -10836,7 +10962,7 @@ class RiskConnectionService {
10836
10962
  payload,
10837
10963
  });
10838
10964
  if (payload) {
10839
- 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);
10840
10966
  this.getRisk.clear(key);
10841
10967
  }
10842
10968
  else {
@@ -12303,7 +12429,7 @@ class ClientAction {
12303
12429
  * @param backtest - Whether running in backtest mode
12304
12430
  * @returns Unique string key for memoization
12305
12431
  */
12306
- const CREATE_KEY_FN$k = (actionName, strategyName, exchangeName, frameName, backtest) => {
12432
+ const CREATE_KEY_FN$m = (actionName, strategyName, exchangeName, frameName, backtest) => {
12307
12433
  const parts = [actionName, strategyName, exchangeName];
12308
12434
  if (frameName)
12309
12435
  parts.push(frameName);
@@ -12354,7 +12480,7 @@ class ActionConnectionService {
12354
12480
  * @param backtest - True if backtest mode, false if live mode
12355
12481
  * @returns Configured ClientAction instance
12356
12482
  */
12357
- 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) => {
12358
12484
  const schema = this.actionSchemaService.get(actionName);
12359
12485
  return new ClientAction({
12360
12486
  ...schema,
@@ -12564,7 +12690,7 @@ class ActionConnectionService {
12564
12690
  await Promise.all(actions.map(async (action) => await action.dispose()));
12565
12691
  return;
12566
12692
  }
12567
- 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);
12568
12694
  if (!this.getAction.has(key)) {
12569
12695
  return;
12570
12696
  }
@@ -12582,7 +12708,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
12582
12708
  * @param exchangeName - Exchange name
12583
12709
  * @returns Unique string key for memoization
12584
12710
  */
12585
- const CREATE_KEY_FN$j = (exchangeName) => {
12711
+ const CREATE_KEY_FN$l = (exchangeName) => {
12586
12712
  return exchangeName;
12587
12713
  };
12588
12714
  /**
@@ -12606,7 +12732,7 @@ class ExchangeCoreService {
12606
12732
  * @param exchangeName - Name of the exchange to validate
12607
12733
  * @returns Promise that resolves when validation is complete
12608
12734
  */
12609
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$j(exchangeName), async (exchangeName) => {
12735
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$l(exchangeName), async (exchangeName) => {
12610
12736
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
12611
12737
  exchangeName,
12612
12738
  });
@@ -12858,7 +12984,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
12858
12984
  * @param context - Execution context with strategyName, exchangeName, frameName
12859
12985
  * @returns Unique string key for memoization
12860
12986
  */
12861
- const CREATE_KEY_FN$i = (context) => {
12987
+ const CREATE_KEY_FN$k = (context) => {
12862
12988
  const parts = [context.strategyName, context.exchangeName];
12863
12989
  if (context.frameName)
12864
12990
  parts.push(context.frameName);
@@ -12890,7 +13016,7 @@ class StrategyCoreService {
12890
13016
  * @param context - Execution context with strategyName, exchangeName, frameName
12891
13017
  * @returns Promise that resolves when validation is complete
12892
13018
  */
12893
- this.validate = memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
13019
+ this.validate = memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
12894
13020
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
12895
13021
  context,
12896
13022
  });
@@ -13725,7 +13851,7 @@ class SizingGlobalService {
13725
13851
  * @param context - Context with riskName, exchangeName, frameName
13726
13852
  * @returns Unique string key for memoization
13727
13853
  */
13728
- const CREATE_KEY_FN$h = (context) => {
13854
+ const CREATE_KEY_FN$j = (context) => {
13729
13855
  const parts = [context.riskName, context.exchangeName];
13730
13856
  if (context.frameName)
13731
13857
  parts.push(context.frameName);
@@ -13751,7 +13877,7 @@ class RiskGlobalService {
13751
13877
  * @param payload - Payload with riskName, exchangeName and frameName
13752
13878
  * @returns Promise that resolves when validation is complete
13753
13879
  */
13754
- this.validate = memoize(([context]) => CREATE_KEY_FN$h(context), async (context) => {
13880
+ this.validate = memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
13755
13881
  this.loggerService.log("riskGlobalService validate", {
13756
13882
  context,
13757
13883
  });
@@ -13829,7 +13955,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
13829
13955
  * @param context - Execution context with strategyName, exchangeName, frameName
13830
13956
  * @returns Unique string key for memoization
13831
13957
  */
13832
- const CREATE_KEY_FN$g = (context) => {
13958
+ const CREATE_KEY_FN$i = (context) => {
13833
13959
  const parts = [context.strategyName, context.exchangeName];
13834
13960
  if (context.frameName)
13835
13961
  parts.push(context.frameName);
@@ -13873,7 +13999,7 @@ class ActionCoreService {
13873
13999
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
13874
14000
  * @returns Promise that resolves when all validations complete
13875
14001
  */
13876
- this.validate = memoize(([context]) => CREATE_KEY_FN$g(context), async (context) => {
14002
+ this.validate = memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
13877
14003
  this.loggerService.log(METHOD_NAME_VALIDATE, {
13878
14004
  context,
13879
14005
  });
@@ -18408,7 +18534,7 @@ const Markdown = new MarkdownAdapter();
18408
18534
  * @param backtest - Whether running in backtest mode
18409
18535
  * @returns Unique string key for memoization
18410
18536
  */
18411
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
18537
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
18412
18538
  const parts = [symbol, strategyName, exchangeName];
18413
18539
  if (frameName)
18414
18540
  parts.push(frameName);
@@ -18647,7 +18773,7 @@ class BacktestMarkdownService {
18647
18773
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
18648
18774
  * Each combination gets its own isolated storage instance.
18649
18775
  */
18650
- 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));
18651
18777
  /**
18652
18778
  * Processes tick events and accumulates closed signals.
18653
18779
  * Should be called from IStrategyCallbacks.onTick.
@@ -18804,7 +18930,7 @@ class BacktestMarkdownService {
18804
18930
  payload,
18805
18931
  });
18806
18932
  if (payload) {
18807
- 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);
18808
18934
  this.getStorage.clear(key);
18809
18935
  }
18810
18936
  else {
@@ -18866,7 +18992,7 @@ class BacktestMarkdownService {
18866
18992
  * @param backtest - Whether running in backtest mode
18867
18993
  * @returns Unique string key for memoization
18868
18994
  */
18869
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
18995
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
18870
18996
  const parts = [symbol, strategyName, exchangeName];
18871
18997
  if (frameName)
18872
18998
  parts.push(frameName);
@@ -19349,7 +19475,7 @@ class LiveMarkdownService {
19349
19475
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19350
19476
  * Each combination gets its own isolated storage instance.
19351
19477
  */
19352
- 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));
19353
19479
  /**
19354
19480
  * Subscribes to live signal emitter to receive tick events.
19355
19481
  * Protected against multiple subscriptions.
@@ -19567,7 +19693,7 @@ class LiveMarkdownService {
19567
19693
  payload,
19568
19694
  });
19569
19695
  if (payload) {
19570
- 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);
19571
19697
  this.getStorage.clear(key);
19572
19698
  }
19573
19699
  else {
@@ -19587,7 +19713,7 @@ class LiveMarkdownService {
19587
19713
  * @param backtest - Whether running in backtest mode
19588
19714
  * @returns Unique string key for memoization
19589
19715
  */
19590
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
19716
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
19591
19717
  const parts = [symbol, strategyName, exchangeName];
19592
19718
  if (frameName)
19593
19719
  parts.push(frameName);
@@ -19878,7 +20004,7 @@ class ScheduleMarkdownService {
19878
20004
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19879
20005
  * Each combination gets its own isolated storage instance.
19880
20006
  */
19881
- 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));
19882
20008
  /**
19883
20009
  * Subscribes to signal emitter to receive scheduled signal events.
19884
20010
  * Protected against multiple subscriptions.
@@ -20081,7 +20207,7 @@ class ScheduleMarkdownService {
20081
20207
  payload,
20082
20208
  });
20083
20209
  if (payload) {
20084
- 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);
20085
20211
  this.getStorage.clear(key);
20086
20212
  }
20087
20213
  else {
@@ -20101,7 +20227,7 @@ class ScheduleMarkdownService {
20101
20227
  * @param backtest - Whether running in backtest mode
20102
20228
  * @returns Unique string key for memoization
20103
20229
  */
20104
- const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
20230
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
20105
20231
  const parts = [symbol, strategyName, exchangeName];
20106
20232
  if (frameName)
20107
20233
  parts.push(frameName);
@@ -20349,7 +20475,7 @@ class PerformanceMarkdownService {
20349
20475
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
20350
20476
  * Each combination gets its own isolated storage instance.
20351
20477
  */
20352
- 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));
20353
20479
  /**
20354
20480
  * Subscribes to performance emitter to receive performance events.
20355
20481
  * Protected against multiple subscriptions.
@@ -20516,7 +20642,7 @@ class PerformanceMarkdownService {
20516
20642
  payload,
20517
20643
  });
20518
20644
  if (payload) {
20519
- 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);
20520
20646
  this.getStorage.clear(key);
20521
20647
  }
20522
20648
  else {
@@ -20986,7 +21112,7 @@ class WalkerMarkdownService {
20986
21112
  * @param backtest - Whether running in backtest mode
20987
21113
  * @returns Unique string key for memoization
20988
21114
  */
20989
- const CREATE_KEY_FN$b = (exchangeName, frameName, backtest) => {
21115
+ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
20990
21116
  const parts = [exchangeName];
20991
21117
  if (frameName)
20992
21118
  parts.push(frameName);
@@ -21353,7 +21479,7 @@ class HeatMarkdownService {
21353
21479
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
21354
21480
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
21355
21481
  */
21356
- 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));
21357
21483
  /**
21358
21484
  * Subscribes to signal emitter to receive tick events.
21359
21485
  * Protected against multiple subscriptions.
@@ -21548,7 +21674,7 @@ class HeatMarkdownService {
21548
21674
  payload,
21549
21675
  });
21550
21676
  if (payload) {
21551
- 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);
21552
21678
  this.getStorage.clear(key);
21553
21679
  }
21554
21680
  else {
@@ -22579,7 +22705,7 @@ class ClientPartial {
22579
22705
  * @param backtest - Whether running in backtest mode
22580
22706
  * @returns Unique string key for memoization
22581
22707
  */
22582
- const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22708
+ const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22583
22709
  /**
22584
22710
  * Creates a callback function for emitting profit events to partialProfitSubject.
22585
22711
  *
@@ -22701,7 +22827,7 @@ class PartialConnectionService {
22701
22827
  * Key format: "signalId:backtest" or "signalId:live"
22702
22828
  * Value: ClientPartial instance with logger and event emitters
22703
22829
  */
22704
- 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) => {
22705
22831
  return new ClientPartial({
22706
22832
  signalId,
22707
22833
  logger: this.loggerService,
@@ -22791,7 +22917,7 @@ class PartialConnectionService {
22791
22917
  const partial = this.getPartial(data.id, backtest);
22792
22918
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
22793
22919
  await partial.clear(symbol, data, priceClose, backtest);
22794
- const key = CREATE_KEY_FN$a(data.id, backtest);
22920
+ const key = CREATE_KEY_FN$c(data.id, backtest);
22795
22921
  this.getPartial.clear(key);
22796
22922
  };
22797
22923
  }
@@ -22807,7 +22933,7 @@ class PartialConnectionService {
22807
22933
  * @param backtest - Whether running in backtest mode
22808
22934
  * @returns Unique string key for memoization
22809
22935
  */
22810
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
22936
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
22811
22937
  const parts = [symbol, strategyName, exchangeName];
22812
22938
  if (frameName)
22813
22939
  parts.push(frameName);
@@ -23032,7 +23158,7 @@ class PartialMarkdownService {
23032
23158
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
23033
23159
  * Each combination gets its own isolated storage instance.
23034
23160
  */
23035
- 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));
23036
23162
  /**
23037
23163
  * Subscribes to partial profit/loss signal emitters to receive events.
23038
23164
  * Protected against multiple subscriptions.
@@ -23242,7 +23368,7 @@ class PartialMarkdownService {
23242
23368
  payload,
23243
23369
  });
23244
23370
  if (payload) {
23245
- 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);
23246
23372
  this.getStorage.clear(key);
23247
23373
  }
23248
23374
  else {
@@ -23258,7 +23384,7 @@ class PartialMarkdownService {
23258
23384
  * @param context - Context with strategyName, exchangeName, frameName
23259
23385
  * @returns Unique string key for memoization
23260
23386
  */
23261
- const CREATE_KEY_FN$8 = (context) => {
23387
+ const CREATE_KEY_FN$a = (context) => {
23262
23388
  const parts = [context.strategyName, context.exchangeName];
23263
23389
  if (context.frameName)
23264
23390
  parts.push(context.frameName);
@@ -23332,7 +23458,7 @@ class PartialGlobalService {
23332
23458
  * @param context - Context with strategyName, exchangeName and frameName
23333
23459
  * @param methodName - Name of the calling method for error tracking
23334
23460
  */
23335
- this.validate = memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
23461
+ this.validate = memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
23336
23462
  this.loggerService.log("partialGlobalService validate", {
23337
23463
  context,
23338
23464
  methodName,
@@ -23787,7 +23913,7 @@ class ClientBreakeven {
23787
23913
  * @param backtest - Whether running in backtest mode
23788
23914
  * @returns Unique string key for memoization
23789
23915
  */
23790
- const CREATE_KEY_FN$7 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23916
+ const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23791
23917
  /**
23792
23918
  * Creates a callback function for emitting breakeven events to breakevenSubject.
23793
23919
  *
@@ -23873,7 +23999,7 @@ class BreakevenConnectionService {
23873
23999
  * Key format: "signalId:backtest" or "signalId:live"
23874
24000
  * Value: ClientBreakeven instance with logger and event emitter
23875
24001
  */
23876
- 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) => {
23877
24003
  return new ClientBreakeven({
23878
24004
  signalId,
23879
24005
  logger: this.loggerService,
@@ -23934,7 +24060,7 @@ class BreakevenConnectionService {
23934
24060
  const breakeven = this.getBreakeven(data.id, backtest);
23935
24061
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
23936
24062
  await breakeven.clear(symbol, data, priceClose, backtest);
23937
- const key = CREATE_KEY_FN$7(data.id, backtest);
24063
+ const key = CREATE_KEY_FN$9(data.id, backtest);
23938
24064
  this.getBreakeven.clear(key);
23939
24065
  };
23940
24066
  }
@@ -23950,7 +24076,7 @@ class BreakevenConnectionService {
23950
24076
  * @param backtest - Whether running in backtest mode
23951
24077
  * @returns Unique string key for memoization
23952
24078
  */
23953
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24079
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
23954
24080
  const parts = [symbol, strategyName, exchangeName];
23955
24081
  if (frameName)
23956
24082
  parts.push(frameName);
@@ -24127,7 +24253,7 @@ class BreakevenMarkdownService {
24127
24253
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24128
24254
  * Each combination gets its own isolated storage instance.
24129
24255
  */
24130
- 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));
24131
24257
  /**
24132
24258
  * Subscribes to breakeven signal emitter to receive events.
24133
24259
  * Protected against multiple subscriptions.
@@ -24316,7 +24442,7 @@ class BreakevenMarkdownService {
24316
24442
  payload,
24317
24443
  });
24318
24444
  if (payload) {
24319
- 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);
24320
24446
  this.getStorage.clear(key);
24321
24447
  }
24322
24448
  else {
@@ -24332,7 +24458,7 @@ class BreakevenMarkdownService {
24332
24458
  * @param context - Context with strategyName, exchangeName, frameName
24333
24459
  * @returns Unique string key for memoization
24334
24460
  */
24335
- const CREATE_KEY_FN$5 = (context) => {
24461
+ const CREATE_KEY_FN$7 = (context) => {
24336
24462
  const parts = [context.strategyName, context.exchangeName];
24337
24463
  if (context.frameName)
24338
24464
  parts.push(context.frameName);
@@ -24406,7 +24532,7 @@ class BreakevenGlobalService {
24406
24532
  * @param context - Context with strategyName, exchangeName and frameName
24407
24533
  * @param methodName - Name of the calling method for error tracking
24408
24534
  */
24409
- this.validate = memoize(([context]) => CREATE_KEY_FN$5(context), (context, methodName) => {
24535
+ this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
24410
24536
  this.loggerService.log("breakevenGlobalService validate", {
24411
24537
  context,
24412
24538
  methodName,
@@ -24626,7 +24752,7 @@ class ConfigValidationService {
24626
24752
  * @param backtest - Whether running in backtest mode
24627
24753
  * @returns Unique string key for memoization
24628
24754
  */
24629
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24755
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24630
24756
  const parts = [symbol, strategyName, exchangeName];
24631
24757
  if (frameName)
24632
24758
  parts.push(frameName);
@@ -24795,7 +24921,7 @@ class RiskMarkdownService {
24795
24921
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24796
24922
  * Each combination gets its own isolated storage instance.
24797
24923
  */
24798
- 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));
24799
24925
  /**
24800
24926
  * Subscribes to risk rejection emitter to receive rejection events.
24801
24927
  * Protected against multiple subscriptions.
@@ -24984,7 +25110,7 @@ class RiskMarkdownService {
24984
25110
  payload,
24985
25111
  });
24986
25112
  if (payload) {
24987
- 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);
24988
25114
  this.getStorage.clear(key);
24989
25115
  }
24990
25116
  else {
@@ -27672,7 +27798,7 @@ class SyncReportService {
27672
27798
  * @returns Colon-separated key string for memoization
27673
27799
  * @internal
27674
27800
  */
27675
- const CREATE_KEY_FN$3 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27801
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27676
27802
  const parts = [symbol, strategyName, exchangeName];
27677
27803
  if (frameName)
27678
27804
  parts.push(frameName);
@@ -27920,7 +28046,7 @@ class StrategyMarkdownService {
27920
28046
  *
27921
28047
  * @internal
27922
28048
  */
27923
- 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));
27924
28050
  /**
27925
28051
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
27926
28052
  *
@@ -28488,7 +28614,7 @@ class StrategyMarkdownService {
28488
28614
  this.clear = async (payload) => {
28489
28615
  this.loggerService.log("strategyMarkdownService clear", { payload });
28490
28616
  if (payload) {
28491
- 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);
28492
28618
  this.getStorage.clear(key);
28493
28619
  }
28494
28620
  else {
@@ -28596,7 +28722,7 @@ class StrategyMarkdownService {
28596
28722
  * Creates a unique key for memoizing ReportStorage instances.
28597
28723
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28598
28724
  */
28599
- const CREATE_KEY_FN$2 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28725
+ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28600
28726
  const parts = [symbol, strategyName, exchangeName];
28601
28727
  if (frameName)
28602
28728
  parts.push(frameName);
@@ -28728,7 +28854,7 @@ class ReportStorage {
28728
28854
  class SyncMarkdownService {
28729
28855
  constructor() {
28730
28856
  this.loggerService = inject(TYPES.loggerService);
28731
- 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));
28732
28858
  this.subscribe = singleshot(() => {
28733
28859
  this.loggerService.log("syncMarkdownService init");
28734
28860
  const unsubscribe = syncSubject.subscribe(this.tick);
@@ -28803,7 +28929,7 @@ class SyncMarkdownService {
28803
28929
  this.clear = async (payload) => {
28804
28930
  this.loggerService.log("syncMarkdownService clear", { payload });
28805
28931
  if (payload) {
28806
- 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);
28807
28933
  this.getStorage.clear(key);
28808
28934
  }
28809
28935
  else {
@@ -28813,6 +28939,275 @@ class SyncMarkdownService {
28813
28939
  }
28814
28940
  }
28815
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
+
28816
29211
  {
28817
29212
  provide(TYPES.loggerService, () => new LoggerService());
28818
29213
  }
@@ -28845,6 +29240,10 @@ class SyncMarkdownService {
28845
29240
  provide(TYPES.actionCoreService, () => new ActionCoreService());
28846
29241
  provide(TYPES.frameCoreService, () => new FrameCoreService());
28847
29242
  }
29243
+ {
29244
+ provide(TYPES.priceMetaService, () => new PriceMetaService());
29245
+ provide(TYPES.timeMetaService, () => new TimeMetaService());
29246
+ }
28848
29247
  {
28849
29248
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
28850
29249
  provide(TYPES.riskGlobalService, () => new RiskGlobalService());
@@ -28936,6 +29335,10 @@ const coreServices = {
28936
29335
  actionCoreService: inject(TYPES.actionCoreService),
28937
29336
  frameCoreService: inject(TYPES.frameCoreService),
28938
29337
  };
29338
+ const metaServices = {
29339
+ timeMetaService: inject(TYPES.timeMetaService),
29340
+ priceMetaService: inject(TYPES.priceMetaService),
29341
+ };
28939
29342
  const globalServices = {
28940
29343
  sizingGlobalService: inject(TYPES.sizingGlobalService),
28941
29344
  riskGlobalService: inject(TYPES.riskGlobalService),
@@ -29000,6 +29403,7 @@ const backtest = {
29000
29403
  ...connectionServices,
29001
29404
  ...schemaServices,
29002
29405
  ...coreServices,
29406
+ ...metaServices,
29003
29407
  ...globalServices,
29004
29408
  ...commandServices,
29005
29409
  ...logicPrivateServices,
@@ -34486,6 +34890,20 @@ class BacktestInstance {
34486
34890
  frameName: context.frameName,
34487
34891
  backtest: true,
34488
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
+ });
34489
34907
  }
34490
34908
  {
34491
34909
  const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
@@ -36390,6 +36808,20 @@ class LiveInstance {
36390
36808
  frameName: "",
36391
36809
  backtest: false,
36392
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
+ });
36393
36825
  }
36394
36826
  {
36395
36827
  const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
@@ -40538,6 +40970,20 @@ class WalkerInstance {
40538
40970
  frameName: walkerSchema.frameName,
40539
40971
  backtest: true,
40540
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
+ });
40541
40987
  }
40542
40988
  {
40543
40989
  const { riskName, riskList, actions } = bt.strategySchemaService.get(strategyName);
@@ -43505,13 +43951,15 @@ class NotificationMemoryBacktestUtils {
43505
43951
  * Handles signal sync events (signal-open, signal-close).
43506
43952
  * @param data - The signal sync contract data
43507
43953
  */
43508
- this.handleSync = async (data) => {
43954
+ this.handleSync = trycatch(async (data) => {
43509
43955
  bt.loggerService.info(NOTIFICATION_MEMORY_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
43510
43956
  signalId: data.signalId,
43511
43957
  action: data.action,
43512
43958
  });
43513
43959
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43514
- };
43960
+ }, {
43961
+ defaultValue: null,
43962
+ });
43515
43963
  /**
43516
43964
  * Handles risk rejection event.
43517
43965
  * @param data - The risk contract data
@@ -43620,8 +44068,10 @@ class NotificationDummyBacktestUtils {
43620
44068
  /**
43621
44069
  * No-op handler for signal sync event.
43622
44070
  */
43623
- this.handleSync = async () => {
43624
- };
44071
+ this.handleSync = trycatch(async () => {
44072
+ }, {
44073
+ defaultValue: null,
44074
+ });
43625
44075
  /**
43626
44076
  * No-op handler for risk rejection event.
43627
44077
  */
@@ -43760,7 +44210,7 @@ class NotificationPersistBacktestUtils {
43760
44210
  * Handles signal sync events (signal-open, signal-close).
43761
44211
  * @param data - The signal sync contract data
43762
44212
  */
43763
- this.handleSync = async (data) => {
44213
+ this.handleSync = trycatch(async (data) => {
43764
44214
  bt.loggerService.info(NOTIFICATION_PERSIST_BACKTEST_METHOD_NAME_HANDLE_SYNC, {
43765
44215
  signalId: data.signalId,
43766
44216
  action: data.action,
@@ -43768,7 +44218,9 @@ class NotificationPersistBacktestUtils {
43768
44218
  await this.waitForInit();
43769
44219
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43770
44220
  await this._updateNotifications();
43771
- };
44221
+ }, {
44222
+ defaultValue: null,
44223
+ });
43772
44224
  /**
43773
44225
  * Handles risk rejection event.
43774
44226
  * @param data - The risk contract data
@@ -43951,13 +44403,15 @@ class NotificationMemoryLiveUtils {
43951
44403
  * Handles signal sync events (signal-open, signal-close).
43952
44404
  * @param data - The signal sync contract data
43953
44405
  */
43954
- this.handleSync = async (data) => {
44406
+ this.handleSync = trycatch(async (data) => {
43955
44407
  bt.loggerService.info(NOTIFICATION_MEMORY_LIVE_METHOD_NAME_HANDLE_SYNC, {
43956
44408
  signalId: data.signalId,
43957
44409
  action: data.action,
43958
44410
  });
43959
44411
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
43960
- };
44412
+ }, {
44413
+ defaultValue: null,
44414
+ });
43961
44415
  /**
43962
44416
  * Handles risk rejection event.
43963
44417
  * @param data - The risk contract data
@@ -44066,8 +44520,10 @@ class NotificationDummyLiveUtils {
44066
44520
  /**
44067
44521
  * No-op handler for signal sync event.
44068
44522
  */
44069
- this.handleSync = async () => {
44070
- };
44523
+ this.handleSync = trycatch(async () => {
44524
+ }, {
44525
+ defaultValue: null,
44526
+ });
44071
44527
  /**
44072
44528
  * No-op handler for risk rejection event.
44073
44529
  */
@@ -44207,7 +44663,7 @@ class NotificationPersistLiveUtils {
44207
44663
  * Handles signal sync events (signal-open, signal-close).
44208
44664
  * @param data - The signal sync contract data
44209
44665
  */
44210
- this.handleSync = async (data) => {
44666
+ this.handleSync = trycatch(async (data) => {
44211
44667
  bt.loggerService.info(NOTIFICATION_PERSIST_LIVE_METHOD_NAME_HANDLE_SYNC, {
44212
44668
  signalId: data.signalId,
44213
44669
  action: data.action,
@@ -44215,7 +44671,9 @@ class NotificationPersistLiveUtils {
44215
44671
  await this.waitForInit();
44216
44672
  this._addNotification(CREATE_SIGNAL_SYNC_NOTIFICATION_FN(data));
44217
44673
  await this._updateNotifications();
44218
- };
44674
+ }, {
44675
+ defaultValue: null,
44676
+ });
44219
44677
  /**
44220
44678
  * Handles risk rejection event.
44221
44679
  * @param data - The risk contract data
@@ -44377,9 +44835,11 @@ class NotificationBacktestAdapter {
44377
44835
  * Proxies call to the underlying notification adapter.
44378
44836
  * @param data - The signal sync contract data
44379
44837
  */
44380
- this.handleSync = async (data) => {
44838
+ this.handleSync = trycatch(async (data) => {
44381
44839
  return await this._notificationBacktestUtils.handleSync(data);
44382
- };
44840
+ }, {
44841
+ defaultValue: null,
44842
+ });
44383
44843
  /**
44384
44844
  * Handles risk rejection event.
44385
44845
  * Proxies call to the underlying notification adapter.
@@ -44521,9 +44981,11 @@ class NotificationLiveAdapter {
44521
44981
  * Proxies call to the underlying notification adapter.
44522
44982
  * @param data - The signal sync contract data
44523
44983
  */
44524
- this.handleSync = async (data) => {
44984
+ this.handleSync = trycatch(async (data) => {
44525
44985
  return await this._notificationLiveUtils.handleSync(data);
44526
- };
44986
+ }, {
44987
+ defaultValue: null,
44988
+ });
44527
44989
  /**
44528
44990
  * Handles risk rejection event.
44529
44991
  * Proxies call to the underlying notification adapter.