backtest-kit 1.5.13 → 1.5.15

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
@@ -165,10 +165,12 @@ const schemaServices$1 = {
165
165
  riskSchemaService: Symbol('riskSchemaService'),
166
166
  optimizerSchemaService: Symbol('optimizerSchemaService'),
167
167
  };
168
+ const coreServices$1 = {
169
+ exchangeCoreService: Symbol('exchangeCoreService'),
170
+ strategyCoreService: Symbol('strategyCoreService'),
171
+ frameCoreService: Symbol('frameCoreService'),
172
+ };
168
173
  const globalServices$1 = {
169
- exchangeGlobalService: Symbol('exchangeGlobalService'),
170
- strategyGlobalService: Symbol('strategyGlobalService'),
171
- frameGlobalService: Symbol('frameGlobalService'),
172
174
  sizingGlobalService: Symbol('sizingGlobalService'),
173
175
  riskGlobalService: Symbol('riskGlobalService'),
174
176
  optimizerGlobalService: Symbol('optimizerGlobalService'),
@@ -217,6 +219,7 @@ const TYPES = {
217
219
  ...contextServices$1,
218
220
  ...connectionServices$1,
219
221
  ...schemaServices$1,
222
+ ...coreServices$1,
220
223
  ...globalServices$1,
221
224
  ...commandServices$1,
222
225
  ...logicPrivateServices$1,
@@ -585,6 +588,14 @@ class ClientExchange {
585
588
  const vwap = sumPriceVolume / totalVolume;
586
589
  return vwap;
587
590
  }
591
+ /**
592
+ * Formats quantity according to exchange-specific rules for the given symbol.
593
+ * Applies proper decimal precision and rounding based on symbol's lot size filters.
594
+ *
595
+ * @param symbol - Trading pair symbol
596
+ * @param quantity - Raw quantity to format
597
+ * @returns Promise resolving to formatted quantity as string
598
+ */
588
599
  async formatQuantity(symbol, quantity) {
589
600
  this.params.logger.debug("binanceService formatQuantity", {
590
601
  symbol,
@@ -592,6 +603,14 @@ class ClientExchange {
592
603
  });
593
604
  return await this.params.formatQuantity(symbol, quantity);
594
605
  }
606
+ /**
607
+ * Formats price according to exchange-specific rules for the given symbol.
608
+ * Applies proper decimal precision and rounding based on symbol's price filters.
609
+ *
610
+ * @param symbol - Trading pair symbol
611
+ * @param price - Raw price to format
612
+ * @returns Promise resolving to formatted price as string
613
+ */
595
614
  async formatPrice(symbol, price) {
596
615
  this.params.logger.debug("binanceService formatPrice", {
597
616
  symbol,
@@ -1999,15 +2018,6 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
1999
2018
  self._lastSignalTimestamp = currentTime;
2000
2019
  }
2001
2020
  const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
2002
- if (await not(self.params.risk.checkSignal({
2003
- symbol: self.params.execution.context.symbol,
2004
- strategyName: self.params.method.context.strategyName,
2005
- exchangeName: self.params.method.context.exchangeName,
2006
- currentPrice,
2007
- timestamp: currentTime,
2008
- }))) {
2009
- return null;
2010
- }
2011
2021
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
2012
2022
  const signal = await Promise.race([
2013
2023
  self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
@@ -2022,6 +2032,16 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
2022
2032
  if (self._isStopped) {
2023
2033
  return null;
2024
2034
  }
2035
+ if (await not(self.params.risk.checkSignal({
2036
+ pendingSignal: signal,
2037
+ symbol: self.params.execution.context.symbol,
2038
+ strategyName: self.params.method.context.strategyName,
2039
+ exchangeName: self.params.method.context.exchangeName,
2040
+ currentPrice,
2041
+ timestamp: currentTime,
2042
+ }))) {
2043
+ return null;
2044
+ }
2025
2045
  // Если priceOpen указан - проверяем нужно ли ждать активации или открыть сразу
2026
2046
  if (signal.priceOpen !== undefined) {
2027
2047
  // КРИТИЧЕСКАЯ ПРОВЕРКА: достигнут ли priceOpen?
@@ -2253,6 +2273,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
2253
2273
  });
2254
2274
  if (await not(self.params.risk.checkSignal({
2255
2275
  symbol: self.params.execution.context.symbol,
2276
+ pendingSignal: scheduled,
2256
2277
  strategyName: self.params.method.context.strategyName,
2257
2278
  exchangeName: self.params.method.context.exchangeName,
2258
2279
  currentPrice: scheduled.priceOpen,
@@ -2336,6 +2357,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
2336
2357
  };
2337
2358
  const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
2338
2359
  if (await not(self.params.risk.checkSignal({
2360
+ pendingSignal: signal,
2339
2361
  symbol: self.params.execution.context.symbol,
2340
2362
  strategyName: self.params.method.context.strategyName,
2341
2363
  exchangeName: self.params.method.context.exchangeName,
@@ -2562,6 +2584,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
2562
2584
  pendingAt: activationTime,
2563
2585
  });
2564
2586
  if (await not(self.params.risk.checkSignal({
2587
+ pendingSignal: scheduled,
2565
2588
  symbol: self.params.execution.context.symbol,
2566
2589
  strategyName: self.params.method.context.strategyName,
2567
2590
  exchangeName: self.params.method.context.exchangeName,
@@ -4023,7 +4046,7 @@ class RiskConnectionService {
4023
4046
  }
4024
4047
  }
4025
4048
 
4026
- const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4049
+ const METHOD_NAME_VALIDATE$1 = "exchangeCoreService validate";
4027
4050
  /**
4028
4051
  * Global service for exchange operations with execution context injection.
4029
4052
  *
@@ -4032,7 +4055,7 @@ const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4032
4055
  *
4033
4056
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4034
4057
  */
4035
- class ExchangeGlobalService {
4058
+ class ExchangeCoreService {
4036
4059
  constructor() {
4037
4060
  this.loggerService = inject(TYPES.loggerService);
4038
4061
  this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
@@ -4062,13 +4085,16 @@ class ExchangeGlobalService {
4062
4085
  * @returns Promise resolving to array of candles
4063
4086
  */
4064
4087
  this.getCandles = async (symbol, interval, limit, when, backtest) => {
4065
- this.loggerService.log("exchangeGlobalService getCandles", {
4088
+ this.loggerService.log("exchangeCoreService getCandles", {
4066
4089
  symbol,
4067
4090
  interval,
4068
4091
  limit,
4069
4092
  when,
4070
4093
  backtest,
4071
4094
  });
4095
+ if (!MethodContextService.hasContext()) {
4096
+ throw new Error("exchangeCoreService getCandles requires a method context");
4097
+ }
4072
4098
  await this.validate(this.methodContextService.context.exchangeName);
4073
4099
  return await ExecutionContextService.runInContext(async () => {
4074
4100
  return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
@@ -4089,13 +4115,16 @@ class ExchangeGlobalService {
4089
4115
  * @returns Promise resolving to array of future candles
4090
4116
  */
4091
4117
  this.getNextCandles = async (symbol, interval, limit, when, backtest) => {
4092
- this.loggerService.log("exchangeGlobalService getNextCandles", {
4118
+ this.loggerService.log("exchangeCoreService getNextCandles", {
4093
4119
  symbol,
4094
4120
  interval,
4095
4121
  limit,
4096
4122
  when,
4097
4123
  backtest,
4098
4124
  });
4125
+ if (!MethodContextService.hasContext()) {
4126
+ throw new Error("exchangeCoreService getNextCandles requires a method context");
4127
+ }
4099
4128
  await this.validate(this.methodContextService.context.exchangeName);
4100
4129
  return await ExecutionContextService.runInContext(async () => {
4101
4130
  return await this.exchangeConnectionService.getNextCandles(symbol, interval, limit);
@@ -4114,11 +4143,14 @@ class ExchangeGlobalService {
4114
4143
  * @returns Promise resolving to VWAP price
4115
4144
  */
4116
4145
  this.getAveragePrice = async (symbol, when, backtest) => {
4117
- this.loggerService.log("exchangeGlobalService getAveragePrice", {
4146
+ this.loggerService.log("exchangeCoreService getAveragePrice", {
4118
4147
  symbol,
4119
4148
  when,
4120
4149
  backtest,
4121
4150
  });
4151
+ if (!MethodContextService.hasContext()) {
4152
+ throw new Error("exchangeCoreService getAveragePrice requires a method context");
4153
+ }
4122
4154
  await this.validate(this.methodContextService.context.exchangeName);
4123
4155
  return await ExecutionContextService.runInContext(async () => {
4124
4156
  return await this.exchangeConnectionService.getAveragePrice(symbol);
@@ -4138,12 +4170,15 @@ class ExchangeGlobalService {
4138
4170
  * @returns Promise resolving to formatted price string
4139
4171
  */
4140
4172
  this.formatPrice = async (symbol, price, when, backtest) => {
4141
- this.loggerService.log("exchangeGlobalService formatPrice", {
4173
+ this.loggerService.log("exchangeCoreService formatPrice", {
4142
4174
  symbol,
4143
4175
  price,
4144
4176
  when,
4145
4177
  backtest,
4146
4178
  });
4179
+ if (!MethodContextService.hasContext()) {
4180
+ throw new Error("exchangeCoreService formatPrice requires a method context");
4181
+ }
4147
4182
  await this.validate(this.methodContextService.context.exchangeName);
4148
4183
  return await ExecutionContextService.runInContext(async () => {
4149
4184
  return await this.exchangeConnectionService.formatPrice(symbol, price);
@@ -4163,12 +4198,15 @@ class ExchangeGlobalService {
4163
4198
  * @returns Promise resolving to formatted quantity string
4164
4199
  */
4165
4200
  this.formatQuantity = async (symbol, quantity, when, backtest) => {
4166
- this.loggerService.log("exchangeGlobalService formatQuantity", {
4201
+ this.loggerService.log("exchangeCoreService formatQuantity", {
4167
4202
  symbol,
4168
4203
  quantity,
4169
4204
  when,
4170
4205
  backtest,
4171
4206
  });
4207
+ if (!MethodContextService.hasContext()) {
4208
+ throw new Error("exchangeCoreService formatQuantity requires a method context");
4209
+ }
4172
4210
  await this.validate(this.methodContextService.context.exchangeName);
4173
4211
  return await ExecutionContextService.runInContext(async () => {
4174
4212
  return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
@@ -4181,7 +4219,7 @@ class ExchangeGlobalService {
4181
4219
  }
4182
4220
  }
4183
4221
 
4184
- const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4222
+ const METHOD_NAME_VALIDATE = "strategyCoreService validate";
4185
4223
  /**
4186
4224
  * Global service for strategy operations with execution context injection.
4187
4225
  *
@@ -4190,7 +4228,7 @@ const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4190
4228
  *
4191
4229
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4192
4230
  */
4193
- class StrategyGlobalService {
4231
+ class StrategyCoreService {
4194
4232
  constructor() {
4195
4233
  this.loggerService = inject(TYPES.loggerService);
4196
4234
  this.strategyConnectionService = inject(TYPES.strategyConnectionService);
@@ -4228,10 +4266,13 @@ class StrategyGlobalService {
4228
4266
  * @returns Promise resolving to pending signal or null
4229
4267
  */
4230
4268
  this.getPendingSignal = async (symbol, strategyName) => {
4231
- this.loggerService.log("strategyGlobalService getPendingSignal", {
4269
+ this.loggerService.log("strategyCoreService getPendingSignal", {
4232
4270
  symbol,
4233
4271
  strategyName,
4234
4272
  });
4273
+ if (!MethodContextService.hasContext()) {
4274
+ throw new Error("strategyCoreService getPendingSignal requires a method context");
4275
+ }
4235
4276
  await this.validate(symbol, strategyName);
4236
4277
  return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
4237
4278
  };
@@ -4246,10 +4287,13 @@ class StrategyGlobalService {
4246
4287
  * @returns Promise resolving to true if strategy is stopped, false otherwise
4247
4288
  */
4248
4289
  this.getStopped = async (symbol, strategyName) => {
4249
- this.loggerService.log("strategyGlobalService getStopped", {
4290
+ this.loggerService.log("strategyCoreService getStopped", {
4250
4291
  symbol,
4251
4292
  strategyName,
4252
4293
  });
4294
+ if (!MethodContextService.hasContext()) {
4295
+ throw new Error("strategyCoreService getStopped requires a method context");
4296
+ }
4253
4297
  await this.validate(symbol, strategyName);
4254
4298
  return await this.strategyConnectionService.getStopped(symbol, strategyName);
4255
4299
  };
@@ -4265,11 +4309,14 @@ class StrategyGlobalService {
4265
4309
  * @returns Discriminated union of tick result (idle, opened, active, closed)
4266
4310
  */
4267
4311
  this.tick = async (symbol, when, backtest) => {
4268
- this.loggerService.log("strategyGlobalService tick", {
4312
+ this.loggerService.log("strategyCoreService tick", {
4269
4313
  symbol,
4270
4314
  when,
4271
4315
  backtest,
4272
4316
  });
4317
+ if (!MethodContextService.hasContext()) {
4318
+ throw new Error("strategyCoreService tick requires a method context");
4319
+ }
4273
4320
  const strategyName = this.methodContextService.context.strategyName;
4274
4321
  await this.validate(symbol, strategyName);
4275
4322
  return await ExecutionContextService.runInContext(async () => {
@@ -4293,12 +4340,15 @@ class StrategyGlobalService {
4293
4340
  * @returns Closed signal result with PNL
4294
4341
  */
4295
4342
  this.backtest = async (symbol, candles, when, backtest) => {
4296
- this.loggerService.log("strategyGlobalService backtest", {
4343
+ this.loggerService.log("strategyCoreService backtest", {
4297
4344
  symbol,
4298
4345
  candleCount: candles.length,
4299
4346
  when,
4300
4347
  backtest,
4301
4348
  });
4349
+ if (!MethodContextService.hasContext()) {
4350
+ throw new Error("strategyCoreService backtest requires a method context");
4351
+ }
4302
4352
  const strategyName = this.methodContextService.context.strategyName;
4303
4353
  await this.validate(symbol, strategyName);
4304
4354
  return await ExecutionContextService.runInContext(async () => {
@@ -4320,7 +4370,7 @@ class StrategyGlobalService {
4320
4370
  * @returns Promise that resolves when stop flag is set
4321
4371
  */
4322
4372
  this.stop = async (ctx, backtest) => {
4323
- this.loggerService.log("strategyGlobalService stop", {
4373
+ this.loggerService.log("strategyCoreService stop", {
4324
4374
  ctx,
4325
4375
  backtest,
4326
4376
  });
@@ -4336,7 +4386,7 @@ class StrategyGlobalService {
4336
4386
  * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
4337
4387
  */
4338
4388
  this.clear = async (ctx) => {
4339
- this.loggerService.log("strategyGlobalService clear", {
4389
+ this.loggerService.log("strategyCoreService clear", {
4340
4390
  ctx,
4341
4391
  });
4342
4392
  if (ctx) {
@@ -4347,14 +4397,14 @@ class StrategyGlobalService {
4347
4397
  }
4348
4398
  }
4349
4399
 
4350
- const METHOD_NAME_GET_TIMEFRAME = "frameGlobalService getTimeframe";
4400
+ const METHOD_NAME_GET_TIMEFRAME = "frameCoreService getTimeframe";
4351
4401
  /**
4352
4402
  * Global service for frame operations.
4353
4403
  *
4354
4404
  * Wraps FrameConnectionService for timeframe generation.
4355
4405
  * Used internally by BacktestLogicPrivateService.
4356
4406
  */
4357
- class FrameGlobalService {
4407
+ class FrameCoreService {
4358
4408
  constructor() {
4359
4409
  this.loggerService = inject(TYPES.loggerService);
4360
4410
  this.frameConnectionService = inject(TYPES.frameConnectionService);
@@ -4370,6 +4420,9 @@ class FrameGlobalService {
4370
4420
  frameName,
4371
4421
  symbol,
4372
4422
  });
4423
+ if (!MethodContextService.hasContext()) {
4424
+ throw new Error("frameCoreService getTimeframe requires a method context");
4425
+ }
4373
4426
  this.frameValidationService.validate(frameName, METHOD_NAME_GET_TIMEFRAME);
4374
4427
  return await this.frameConnectionService.getTimeframe(symbol, frameName);
4375
4428
  };
@@ -4975,9 +5028,9 @@ class WalkerSchemaService {
4975
5028
  class BacktestLogicPrivateService {
4976
5029
  constructor() {
4977
5030
  this.loggerService = inject(TYPES.loggerService);
4978
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
4979
- this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
4980
- this.frameGlobalService = inject(TYPES.frameGlobalService);
5031
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5032
+ this.exchangeCoreService = inject(TYPES.exchangeCoreService);
5033
+ this.frameCoreService = inject(TYPES.frameCoreService);
4981
5034
  this.methodContextService = inject(TYPES.methodContextService);
4982
5035
  }
4983
5036
  /**
@@ -4999,7 +5052,7 @@ class BacktestLogicPrivateService {
4999
5052
  symbol,
5000
5053
  });
5001
5054
  const backtestStartTime = performance.now();
5002
- const timeframes = await this.frameGlobalService.getTimeframe(symbol, this.methodContextService.context.frameName);
5055
+ const timeframes = await this.frameCoreService.getTimeframe(symbol, this.methodContextService.context.frameName);
5003
5056
  const totalFrames = timeframes.length;
5004
5057
  let i = 0;
5005
5058
  let previousEventTimestamp = null;
@@ -5018,7 +5071,7 @@ class BacktestLogicPrivateService {
5018
5071
  });
5019
5072
  }
5020
5073
  // Check if strategy should stop before processing next frame
5021
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5074
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5022
5075
  this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
5023
5076
  symbol,
5024
5077
  when: when.toISOString(),
@@ -5029,7 +5082,7 @@ class BacktestLogicPrivateService {
5029
5082
  }
5030
5083
  let result;
5031
5084
  try {
5032
- result = await this.strategyGlobalService.tick(symbol, when, true);
5085
+ result = await this.strategyCoreService.tick(symbol, when, true);
5033
5086
  }
5034
5087
  catch (error) {
5035
5088
  console.warn(`backtestLogicPrivateService tick failed, skipping timeframe when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5043,7 +5096,7 @@ class BacktestLogicPrivateService {
5043
5096
  continue;
5044
5097
  }
5045
5098
  // Check if strategy should stop when idle (no active signal)
5046
- if (await and(Promise.resolve(result.action === "idle"), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5099
+ if (await and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5047
5100
  this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
5048
5101
  symbol,
5049
5102
  when: when.toISOString(),
@@ -5073,7 +5126,7 @@ class BacktestLogicPrivateService {
5073
5126
  const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
5074
5127
  let candles;
5075
5128
  try {
5076
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5129
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5077
5130
  }
5078
5131
  catch (error) {
5079
5132
  console.warn(`backtestLogicPrivateService getNextCandles failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5102,7 +5155,7 @@ class BacktestLogicPrivateService {
5102
5155
  // и если активируется - продолжит с TP/SL мониторингом
5103
5156
  let backtestResult;
5104
5157
  try {
5105
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5158
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5106
5159
  }
5107
5160
  catch (error) {
5108
5161
  console.warn(`backtestLogicPrivateService backtest failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5145,7 +5198,7 @@ class BacktestLogicPrivateService {
5145
5198
  }
5146
5199
  yield backtestResult;
5147
5200
  // Check if strategy should stop after signal is closed
5148
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5201
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5149
5202
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
5150
5203
  symbol,
5151
5204
  signalId: backtestResult.signal.id,
@@ -5172,7 +5225,7 @@ class BacktestLogicPrivateService {
5172
5225
  const totalCandles = signal.minuteEstimatedTime + bufferMinutes;
5173
5226
  let candles;
5174
5227
  try {
5175
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5228
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5176
5229
  }
5177
5230
  catch (error) {
5178
5231
  console.warn(`backtestLogicPrivateService getNextCandles failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5199,7 +5252,7 @@ class BacktestLogicPrivateService {
5199
5252
  // Вызываем backtest - всегда возвращает closed
5200
5253
  let backtestResult;
5201
5254
  try {
5202
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5255
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5203
5256
  }
5204
5257
  catch (error) {
5205
5258
  console.warn(`backtestLogicPrivateService backtest failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5238,7 +5291,7 @@ class BacktestLogicPrivateService {
5238
5291
  }
5239
5292
  yield backtestResult;
5240
5293
  // Check if strategy should stop after signal is closed
5241
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5294
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5242
5295
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
5243
5296
  symbol,
5244
5297
  signalId: backtestResult.signal.id,
@@ -5312,7 +5365,7 @@ const TICK_TTL = 1 * 60 * 1000 + 1;
5312
5365
  class LiveLogicPrivateService {
5313
5366
  constructor() {
5314
5367
  this.loggerService = inject(TYPES.loggerService);
5315
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
5368
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5316
5369
  this.methodContextService = inject(TYPES.methodContextService);
5317
5370
  }
5318
5371
  /**
@@ -5347,7 +5400,7 @@ class LiveLogicPrivateService {
5347
5400
  const when = new Date();
5348
5401
  let result;
5349
5402
  try {
5350
- result = await this.strategyGlobalService.tick(symbol, when, false);
5403
+ result = await this.strategyCoreService.tick(symbol, when, false);
5351
5404
  }
5352
5405
  catch (error) {
5353
5406
  console.warn(`backtestLogicPrivateService tick failed when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5381,7 +5434,7 @@ class LiveLogicPrivateService {
5381
5434
  previousEventTimestamp = currentTimestamp;
5382
5435
  // Check if strategy should stop when idle (no active signal)
5383
5436
  if (result.action === "idle") {
5384
- if (await and(Promise.resolve(true), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5437
+ if (await and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5385
5438
  this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
5386
5439
  symbol,
5387
5440
  when: when.toISOString(),
@@ -5403,7 +5456,7 @@ class LiveLogicPrivateService {
5403
5456
  yield result;
5404
5457
  // Check if strategy should stop after signal is closed
5405
5458
  if (result.action === "closed") {
5406
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5459
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5407
5460
  this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
5408
5461
  symbol,
5409
5462
  signalId: result.signal.id,
@@ -6494,14 +6547,11 @@ let ReportStorage$3 = class ReportStorage {
6494
6547
  }
6495
6548
  }
6496
6549
  /**
6497
- * Updates or adds an active event to the storage.
6498
- * Replaces the previous event with the same signalId.
6550
+ * Adds an active event to the storage.
6499
6551
  *
6500
6552
  * @param data - Active tick result
6501
6553
  */
6502
6554
  addActiveEvent(data) {
6503
- // Find existing event with the same signalId
6504
- const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
6505
6555
  const newEvent = {
6506
6556
  timestamp: Date.now(),
6507
6557
  action: "active",
@@ -6516,29 +6566,20 @@ let ReportStorage$3 = class ReportStorage {
6516
6566
  percentTp: data.percentTp,
6517
6567
  percentSl: data.percentSl,
6518
6568
  };
6519
- // Replace existing event or add new one
6520
- if (existingIndex !== -1) {
6521
- this._eventList[existingIndex] = newEvent;
6522
- }
6523
- else {
6524
- this._eventList.push(newEvent);
6525
- // Trim queue if exceeded MAX_EVENTS
6526
- if (this._eventList.length > MAX_EVENTS$3) {
6527
- this._eventList.shift();
6528
- }
6569
+ this._eventList.push(newEvent);
6570
+ // Trim queue if exceeded MAX_EVENTS
6571
+ if (this._eventList.length > MAX_EVENTS$3) {
6572
+ this._eventList.shift();
6529
6573
  }
6530
6574
  }
6531
6575
  /**
6532
- * Updates or adds a closed event to the storage.
6533
- * Replaces the previous event with the same signalId.
6576
+ * Adds a closed event to the storage.
6534
6577
  *
6535
6578
  * @param data - Closed tick result
6536
6579
  */
6537
6580
  addClosedEvent(data) {
6538
6581
  const durationMs = data.closeTimestamp - data.signal.pendingAt;
6539
6582
  const durationMin = Math.round(durationMs / 60000);
6540
- // Find existing event with the same signalId
6541
- const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
6542
6583
  const newEvent = {
6543
6584
  timestamp: data.closeTimestamp,
6544
6585
  action: "closed",
@@ -6554,16 +6595,10 @@ let ReportStorage$3 = class ReportStorage {
6554
6595
  closeReason: data.closeReason,
6555
6596
  duration: durationMin,
6556
6597
  };
6557
- // Replace existing event or add new one
6558
- if (existingIndex !== -1) {
6559
- this._eventList[existingIndex] = newEvent;
6560
- }
6561
- else {
6562
- this._eventList.push(newEvent);
6563
- // Trim queue if exceeded MAX_EVENTS
6564
- if (this._eventList.length > MAX_EVENTS$3) {
6565
- this._eventList.shift();
6566
- }
6598
+ this._eventList.push(newEvent);
6599
+ // Trim queue if exceeded MAX_EVENTS
6600
+ if (this._eventList.length > MAX_EVENTS$3) {
6601
+ this._eventList.shift();
6567
6602
  }
6568
6603
  }
6569
6604
  /**
@@ -6998,16 +7033,13 @@ let ReportStorage$2 = class ReportStorage {
6998
7033
  }
6999
7034
  }
7000
7035
  /**
7001
- * Updates or adds a cancelled event to the storage.
7002
- * Replaces the previous event with the same signalId.
7036
+ * Adds a cancelled event to the storage.
7003
7037
  *
7004
7038
  * @param data - Cancelled tick result
7005
7039
  */
7006
7040
  addCancelledEvent(data) {
7007
7041
  const durationMs = data.closeTimestamp - data.signal.scheduledAt;
7008
7042
  const durationMin = Math.round(durationMs / 60000);
7009
- // Find existing event with the same signalId
7010
- const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
7011
7043
  const newEvent = {
7012
7044
  timestamp: data.closeTimestamp,
7013
7045
  action: "cancelled",
@@ -7022,16 +7054,10 @@ let ReportStorage$2 = class ReportStorage {
7022
7054
  closeTimestamp: data.closeTimestamp,
7023
7055
  duration: durationMin,
7024
7056
  };
7025
- // Replace existing event or add new one
7026
- if (existingIndex !== -1) {
7027
- this._eventList[existingIndex] = newEvent;
7028
- }
7029
- else {
7030
- this._eventList.push(newEvent);
7031
- // Trim queue if exceeded MAX_EVENTS
7032
- if (this._eventList.length > MAX_EVENTS$2) {
7033
- this._eventList.shift();
7034
- }
7057
+ this._eventList.push(newEvent);
7058
+ // Trim queue if exceeded MAX_EVENTS
7059
+ if (this._eventList.length > MAX_EVENTS$2) {
7060
+ this._eventList.shift();
7035
7061
  }
7036
7062
  }
7037
7063
  /**
@@ -11839,9 +11865,11 @@ class ConfigValidationService {
11839
11865
  provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
11840
11866
  }
11841
11867
  {
11842
- provide(TYPES.exchangeGlobalService, () => new ExchangeGlobalService());
11843
- provide(TYPES.strategyGlobalService, () => new StrategyGlobalService());
11844
- provide(TYPES.frameGlobalService, () => new FrameGlobalService());
11868
+ provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
11869
+ provide(TYPES.strategyCoreService, () => new StrategyCoreService());
11870
+ provide(TYPES.frameCoreService, () => new FrameCoreService());
11871
+ }
11872
+ {
11845
11873
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
11846
11874
  provide(TYPES.riskGlobalService, () => new RiskGlobalService());
11847
11875
  provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
@@ -11911,10 +11939,12 @@ const schemaServices = {
11911
11939
  riskSchemaService: inject(TYPES.riskSchemaService),
11912
11940
  optimizerSchemaService: inject(TYPES.optimizerSchemaService),
11913
11941
  };
11942
+ const coreServices = {
11943
+ exchangeCoreService: inject(TYPES.exchangeCoreService),
11944
+ strategyCoreService: inject(TYPES.strategyCoreService),
11945
+ frameCoreService: inject(TYPES.frameCoreService),
11946
+ };
11914
11947
  const globalServices = {
11915
- exchangeGlobalService: inject(TYPES.exchangeGlobalService),
11916
- strategyGlobalService: inject(TYPES.strategyGlobalService),
11917
- frameGlobalService: inject(TYPES.frameGlobalService),
11918
11948
  sizingGlobalService: inject(TYPES.sizingGlobalService),
11919
11949
  riskGlobalService: inject(TYPES.riskGlobalService),
11920
11950
  optimizerGlobalService: inject(TYPES.optimizerGlobalService),
@@ -11963,6 +11993,7 @@ const backtest = {
11963
11993
  ...contextServices,
11964
11994
  ...connectionServices,
11965
11995
  ...schemaServices,
11996
+ ...coreServices,
11966
11997
  ...globalServices,
11967
11998
  ...commandServices,
11968
11999
  ...logicPrivateServices,
@@ -11997,6 +12028,7 @@ function setLogger(logger) {
11997
12028
  /**
11998
12029
  * Sets global configuration parameters for the framework.
11999
12030
  * @param config - Partial configuration object to override default settings
12031
+ * @param _unsafe - Skip config validations - required for testbed
12000
12032
  *
12001
12033
  * @example
12002
12034
  * ```typescript
@@ -13859,7 +13891,7 @@ class BacktestInstance {
13859
13891
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
13860
13892
  }
13861
13893
  {
13862
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
13894
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
13863
13895
  }
13864
13896
  {
13865
13897
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -13894,8 +13926,8 @@ class BacktestInstance {
13894
13926
  });
13895
13927
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
13896
13928
  return () => {
13897
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
13898
- backtest$1.strategyGlobalService
13929
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, true);
13930
+ backtest$1.strategyCoreService
13899
13931
  .getPendingSignal(symbol, context.strategyName)
13900
13932
  .then(async (pendingSignal) => {
13901
13933
  if (pendingSignal) {
@@ -13936,7 +13968,7 @@ class BacktestInstance {
13936
13968
  symbol,
13937
13969
  strategyName,
13938
13970
  });
13939
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
13971
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
13940
13972
  };
13941
13973
  /**
13942
13974
  * Gets statistical data from all closed signals for a symbol-strategy pair.
@@ -14351,7 +14383,7 @@ class LiveInstance {
14351
14383
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
14352
14384
  }
14353
14385
  {
14354
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
14386
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
14355
14387
  }
14356
14388
  {
14357
14389
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -14386,8 +14418,8 @@ class LiveInstance {
14386
14418
  });
14387
14419
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
14388
14420
  return () => {
14389
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
14390
- backtest$1.strategyGlobalService
14421
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, false);
14422
+ backtest$1.strategyCoreService
14391
14423
  .getPendingSignal(symbol, context.strategyName)
14392
14424
  .then(async (pendingSignal) => {
14393
14425
  if (pendingSignal) {
@@ -14428,7 +14460,7 @@ class LiveInstance {
14428
14460
  symbol,
14429
14461
  strategyName,
14430
14462
  });
14431
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
14463
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, false);
14432
14464
  };
14433
14465
  /**
14434
14466
  * Gets statistical data from all live trading events for a symbol-strategy pair.
@@ -15109,7 +15141,7 @@ class WalkerInstance {
15109
15141
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
15110
15142
  }
15111
15143
  {
15112
- backtest$1.strategyGlobalService.clear({ symbol, strategyName });
15144
+ backtest$1.strategyCoreService.clear({ symbol, strategyName });
15113
15145
  }
15114
15146
  {
15115
15147
  const { riskName } = backtest$1.strategySchemaService.get(strategyName);
@@ -15149,7 +15181,7 @@ class WalkerInstance {
15149
15181
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
15150
15182
  return () => {
15151
15183
  for (const strategyName of walkerSchema.strategies) {
15152
- backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15184
+ backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15153
15185
  walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
15154
15186
  }
15155
15187
  if (!this._isDone) {
@@ -15195,7 +15227,7 @@ class WalkerInstance {
15195
15227
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
15196
15228
  for (const strategyName of walkerSchema.strategies) {
15197
15229
  await walkerStopSubject.next({ symbol, strategyName, walkerName });
15198
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15230
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15199
15231
  }
15200
15232
  };
15201
15233
  /**