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.cjs CHANGED
@@ -167,10 +167,12 @@ const schemaServices$1 = {
167
167
  riskSchemaService: Symbol('riskSchemaService'),
168
168
  optimizerSchemaService: Symbol('optimizerSchemaService'),
169
169
  };
170
+ const coreServices$1 = {
171
+ exchangeCoreService: Symbol('exchangeCoreService'),
172
+ strategyCoreService: Symbol('strategyCoreService'),
173
+ frameCoreService: Symbol('frameCoreService'),
174
+ };
170
175
  const globalServices$1 = {
171
- exchangeGlobalService: Symbol('exchangeGlobalService'),
172
- strategyGlobalService: Symbol('strategyGlobalService'),
173
- frameGlobalService: Symbol('frameGlobalService'),
174
176
  sizingGlobalService: Symbol('sizingGlobalService'),
175
177
  riskGlobalService: Symbol('riskGlobalService'),
176
178
  optimizerGlobalService: Symbol('optimizerGlobalService'),
@@ -219,6 +221,7 @@ const TYPES = {
219
221
  ...contextServices$1,
220
222
  ...connectionServices$1,
221
223
  ...schemaServices$1,
224
+ ...coreServices$1,
222
225
  ...globalServices$1,
223
226
  ...commandServices$1,
224
227
  ...logicPrivateServices$1,
@@ -587,6 +590,14 @@ class ClientExchange {
587
590
  const vwap = sumPriceVolume / totalVolume;
588
591
  return vwap;
589
592
  }
593
+ /**
594
+ * Formats quantity according to exchange-specific rules for the given symbol.
595
+ * Applies proper decimal precision and rounding based on symbol's lot size filters.
596
+ *
597
+ * @param symbol - Trading pair symbol
598
+ * @param quantity - Raw quantity to format
599
+ * @returns Promise resolving to formatted quantity as string
600
+ */
590
601
  async formatQuantity(symbol, quantity) {
591
602
  this.params.logger.debug("binanceService formatQuantity", {
592
603
  symbol,
@@ -594,6 +605,14 @@ class ClientExchange {
594
605
  });
595
606
  return await this.params.formatQuantity(symbol, quantity);
596
607
  }
608
+ /**
609
+ * Formats price according to exchange-specific rules for the given symbol.
610
+ * Applies proper decimal precision and rounding based on symbol's price filters.
611
+ *
612
+ * @param symbol - Trading pair symbol
613
+ * @param price - Raw price to format
614
+ * @returns Promise resolving to formatted price as string
615
+ */
597
616
  async formatPrice(symbol, price) {
598
617
  this.params.logger.debug("binanceService formatPrice", {
599
618
  symbol,
@@ -2001,15 +2020,6 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
2001
2020
  self._lastSignalTimestamp = currentTime;
2002
2021
  }
2003
2022
  const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
2004
- if (await functoolsKit.not(self.params.risk.checkSignal({
2005
- symbol: self.params.execution.context.symbol,
2006
- strategyName: self.params.method.context.strategyName,
2007
- exchangeName: self.params.method.context.exchangeName,
2008
- currentPrice,
2009
- timestamp: currentTime,
2010
- }))) {
2011
- return null;
2012
- }
2013
2023
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
2014
2024
  const signal = await Promise.race([
2015
2025
  self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
@@ -2024,6 +2034,16 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
2024
2034
  if (self._isStopped) {
2025
2035
  return null;
2026
2036
  }
2037
+ if (await functoolsKit.not(self.params.risk.checkSignal({
2038
+ pendingSignal: signal,
2039
+ symbol: self.params.execution.context.symbol,
2040
+ strategyName: self.params.method.context.strategyName,
2041
+ exchangeName: self.params.method.context.exchangeName,
2042
+ currentPrice,
2043
+ timestamp: currentTime,
2044
+ }))) {
2045
+ return null;
2046
+ }
2027
2047
  // Если priceOpen указан - проверяем нужно ли ждать активации или открыть сразу
2028
2048
  if (signal.priceOpen !== undefined) {
2029
2049
  // КРИТИЧЕСКАЯ ПРОВЕРКА: достигнут ли priceOpen?
@@ -2255,6 +2275,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
2255
2275
  });
2256
2276
  if (await functoolsKit.not(self.params.risk.checkSignal({
2257
2277
  symbol: self.params.execution.context.symbol,
2278
+ pendingSignal: scheduled,
2258
2279
  strategyName: self.params.method.context.strategyName,
2259
2280
  exchangeName: self.params.method.context.exchangeName,
2260
2281
  currentPrice: scheduled.priceOpen,
@@ -2338,6 +2359,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
2338
2359
  };
2339
2360
  const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
2340
2361
  if (await functoolsKit.not(self.params.risk.checkSignal({
2362
+ pendingSignal: signal,
2341
2363
  symbol: self.params.execution.context.symbol,
2342
2364
  strategyName: self.params.method.context.strategyName,
2343
2365
  exchangeName: self.params.method.context.exchangeName,
@@ -2564,6 +2586,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
2564
2586
  pendingAt: activationTime,
2565
2587
  });
2566
2588
  if (await functoolsKit.not(self.params.risk.checkSignal({
2589
+ pendingSignal: scheduled,
2567
2590
  symbol: self.params.execution.context.symbol,
2568
2591
  strategyName: self.params.method.context.strategyName,
2569
2592
  exchangeName: self.params.method.context.exchangeName,
@@ -4025,7 +4048,7 @@ class RiskConnectionService {
4025
4048
  }
4026
4049
  }
4027
4050
 
4028
- const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4051
+ const METHOD_NAME_VALIDATE$1 = "exchangeCoreService validate";
4029
4052
  /**
4030
4053
  * Global service for exchange operations with execution context injection.
4031
4054
  *
@@ -4034,7 +4057,7 @@ const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4034
4057
  *
4035
4058
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4036
4059
  */
4037
- class ExchangeGlobalService {
4060
+ class ExchangeCoreService {
4038
4061
  constructor() {
4039
4062
  this.loggerService = inject(TYPES.loggerService);
4040
4063
  this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
@@ -4064,13 +4087,16 @@ class ExchangeGlobalService {
4064
4087
  * @returns Promise resolving to array of candles
4065
4088
  */
4066
4089
  this.getCandles = async (symbol, interval, limit, when, backtest) => {
4067
- this.loggerService.log("exchangeGlobalService getCandles", {
4090
+ this.loggerService.log("exchangeCoreService getCandles", {
4068
4091
  symbol,
4069
4092
  interval,
4070
4093
  limit,
4071
4094
  when,
4072
4095
  backtest,
4073
4096
  });
4097
+ if (!MethodContextService.hasContext()) {
4098
+ throw new Error("exchangeCoreService getCandles requires a method context");
4099
+ }
4074
4100
  await this.validate(this.methodContextService.context.exchangeName);
4075
4101
  return await ExecutionContextService.runInContext(async () => {
4076
4102
  return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
@@ -4091,13 +4117,16 @@ class ExchangeGlobalService {
4091
4117
  * @returns Promise resolving to array of future candles
4092
4118
  */
4093
4119
  this.getNextCandles = async (symbol, interval, limit, when, backtest) => {
4094
- this.loggerService.log("exchangeGlobalService getNextCandles", {
4120
+ this.loggerService.log("exchangeCoreService getNextCandles", {
4095
4121
  symbol,
4096
4122
  interval,
4097
4123
  limit,
4098
4124
  when,
4099
4125
  backtest,
4100
4126
  });
4127
+ if (!MethodContextService.hasContext()) {
4128
+ throw new Error("exchangeCoreService getNextCandles requires a method context");
4129
+ }
4101
4130
  await this.validate(this.methodContextService.context.exchangeName);
4102
4131
  return await ExecutionContextService.runInContext(async () => {
4103
4132
  return await this.exchangeConnectionService.getNextCandles(symbol, interval, limit);
@@ -4116,11 +4145,14 @@ class ExchangeGlobalService {
4116
4145
  * @returns Promise resolving to VWAP price
4117
4146
  */
4118
4147
  this.getAveragePrice = async (symbol, when, backtest) => {
4119
- this.loggerService.log("exchangeGlobalService getAveragePrice", {
4148
+ this.loggerService.log("exchangeCoreService getAveragePrice", {
4120
4149
  symbol,
4121
4150
  when,
4122
4151
  backtest,
4123
4152
  });
4153
+ if (!MethodContextService.hasContext()) {
4154
+ throw new Error("exchangeCoreService getAveragePrice requires a method context");
4155
+ }
4124
4156
  await this.validate(this.methodContextService.context.exchangeName);
4125
4157
  return await ExecutionContextService.runInContext(async () => {
4126
4158
  return await this.exchangeConnectionService.getAveragePrice(symbol);
@@ -4140,12 +4172,15 @@ class ExchangeGlobalService {
4140
4172
  * @returns Promise resolving to formatted price string
4141
4173
  */
4142
4174
  this.formatPrice = async (symbol, price, when, backtest) => {
4143
- this.loggerService.log("exchangeGlobalService formatPrice", {
4175
+ this.loggerService.log("exchangeCoreService formatPrice", {
4144
4176
  symbol,
4145
4177
  price,
4146
4178
  when,
4147
4179
  backtest,
4148
4180
  });
4181
+ if (!MethodContextService.hasContext()) {
4182
+ throw new Error("exchangeCoreService formatPrice requires a method context");
4183
+ }
4149
4184
  await this.validate(this.methodContextService.context.exchangeName);
4150
4185
  return await ExecutionContextService.runInContext(async () => {
4151
4186
  return await this.exchangeConnectionService.formatPrice(symbol, price);
@@ -4165,12 +4200,15 @@ class ExchangeGlobalService {
4165
4200
  * @returns Promise resolving to formatted quantity string
4166
4201
  */
4167
4202
  this.formatQuantity = async (symbol, quantity, when, backtest) => {
4168
- this.loggerService.log("exchangeGlobalService formatQuantity", {
4203
+ this.loggerService.log("exchangeCoreService formatQuantity", {
4169
4204
  symbol,
4170
4205
  quantity,
4171
4206
  when,
4172
4207
  backtest,
4173
4208
  });
4209
+ if (!MethodContextService.hasContext()) {
4210
+ throw new Error("exchangeCoreService formatQuantity requires a method context");
4211
+ }
4174
4212
  await this.validate(this.methodContextService.context.exchangeName);
4175
4213
  return await ExecutionContextService.runInContext(async () => {
4176
4214
  return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
@@ -4183,7 +4221,7 @@ class ExchangeGlobalService {
4183
4221
  }
4184
4222
  }
4185
4223
 
4186
- const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4224
+ const METHOD_NAME_VALIDATE = "strategyCoreService validate";
4187
4225
  /**
4188
4226
  * Global service for strategy operations with execution context injection.
4189
4227
  *
@@ -4192,7 +4230,7 @@ const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4192
4230
  *
4193
4231
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4194
4232
  */
4195
- class StrategyGlobalService {
4233
+ class StrategyCoreService {
4196
4234
  constructor() {
4197
4235
  this.loggerService = inject(TYPES.loggerService);
4198
4236
  this.strategyConnectionService = inject(TYPES.strategyConnectionService);
@@ -4230,10 +4268,13 @@ class StrategyGlobalService {
4230
4268
  * @returns Promise resolving to pending signal or null
4231
4269
  */
4232
4270
  this.getPendingSignal = async (symbol, strategyName) => {
4233
- this.loggerService.log("strategyGlobalService getPendingSignal", {
4271
+ this.loggerService.log("strategyCoreService getPendingSignal", {
4234
4272
  symbol,
4235
4273
  strategyName,
4236
4274
  });
4275
+ if (!MethodContextService.hasContext()) {
4276
+ throw new Error("strategyCoreService getPendingSignal requires a method context");
4277
+ }
4237
4278
  await this.validate(symbol, strategyName);
4238
4279
  return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
4239
4280
  };
@@ -4248,10 +4289,13 @@ class StrategyGlobalService {
4248
4289
  * @returns Promise resolving to true if strategy is stopped, false otherwise
4249
4290
  */
4250
4291
  this.getStopped = async (symbol, strategyName) => {
4251
- this.loggerService.log("strategyGlobalService getStopped", {
4292
+ this.loggerService.log("strategyCoreService getStopped", {
4252
4293
  symbol,
4253
4294
  strategyName,
4254
4295
  });
4296
+ if (!MethodContextService.hasContext()) {
4297
+ throw new Error("strategyCoreService getStopped requires a method context");
4298
+ }
4255
4299
  await this.validate(symbol, strategyName);
4256
4300
  return await this.strategyConnectionService.getStopped(symbol, strategyName);
4257
4301
  };
@@ -4267,11 +4311,14 @@ class StrategyGlobalService {
4267
4311
  * @returns Discriminated union of tick result (idle, opened, active, closed)
4268
4312
  */
4269
4313
  this.tick = async (symbol, when, backtest) => {
4270
- this.loggerService.log("strategyGlobalService tick", {
4314
+ this.loggerService.log("strategyCoreService tick", {
4271
4315
  symbol,
4272
4316
  when,
4273
4317
  backtest,
4274
4318
  });
4319
+ if (!MethodContextService.hasContext()) {
4320
+ throw new Error("strategyCoreService tick requires a method context");
4321
+ }
4275
4322
  const strategyName = this.methodContextService.context.strategyName;
4276
4323
  await this.validate(symbol, strategyName);
4277
4324
  return await ExecutionContextService.runInContext(async () => {
@@ -4295,12 +4342,15 @@ class StrategyGlobalService {
4295
4342
  * @returns Closed signal result with PNL
4296
4343
  */
4297
4344
  this.backtest = async (symbol, candles, when, backtest) => {
4298
- this.loggerService.log("strategyGlobalService backtest", {
4345
+ this.loggerService.log("strategyCoreService backtest", {
4299
4346
  symbol,
4300
4347
  candleCount: candles.length,
4301
4348
  when,
4302
4349
  backtest,
4303
4350
  });
4351
+ if (!MethodContextService.hasContext()) {
4352
+ throw new Error("strategyCoreService backtest requires a method context");
4353
+ }
4304
4354
  const strategyName = this.methodContextService.context.strategyName;
4305
4355
  await this.validate(symbol, strategyName);
4306
4356
  return await ExecutionContextService.runInContext(async () => {
@@ -4322,7 +4372,7 @@ class StrategyGlobalService {
4322
4372
  * @returns Promise that resolves when stop flag is set
4323
4373
  */
4324
4374
  this.stop = async (ctx, backtest) => {
4325
- this.loggerService.log("strategyGlobalService stop", {
4375
+ this.loggerService.log("strategyCoreService stop", {
4326
4376
  ctx,
4327
4377
  backtest,
4328
4378
  });
@@ -4338,7 +4388,7 @@ class StrategyGlobalService {
4338
4388
  * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
4339
4389
  */
4340
4390
  this.clear = async (ctx) => {
4341
- this.loggerService.log("strategyGlobalService clear", {
4391
+ this.loggerService.log("strategyCoreService clear", {
4342
4392
  ctx,
4343
4393
  });
4344
4394
  if (ctx) {
@@ -4349,14 +4399,14 @@ class StrategyGlobalService {
4349
4399
  }
4350
4400
  }
4351
4401
 
4352
- const METHOD_NAME_GET_TIMEFRAME = "frameGlobalService getTimeframe";
4402
+ const METHOD_NAME_GET_TIMEFRAME = "frameCoreService getTimeframe";
4353
4403
  /**
4354
4404
  * Global service for frame operations.
4355
4405
  *
4356
4406
  * Wraps FrameConnectionService for timeframe generation.
4357
4407
  * Used internally by BacktestLogicPrivateService.
4358
4408
  */
4359
- class FrameGlobalService {
4409
+ class FrameCoreService {
4360
4410
  constructor() {
4361
4411
  this.loggerService = inject(TYPES.loggerService);
4362
4412
  this.frameConnectionService = inject(TYPES.frameConnectionService);
@@ -4372,6 +4422,9 @@ class FrameGlobalService {
4372
4422
  frameName,
4373
4423
  symbol,
4374
4424
  });
4425
+ if (!MethodContextService.hasContext()) {
4426
+ throw new Error("frameCoreService getTimeframe requires a method context");
4427
+ }
4375
4428
  this.frameValidationService.validate(frameName, METHOD_NAME_GET_TIMEFRAME);
4376
4429
  return await this.frameConnectionService.getTimeframe(symbol, frameName);
4377
4430
  };
@@ -4977,9 +5030,9 @@ class WalkerSchemaService {
4977
5030
  class BacktestLogicPrivateService {
4978
5031
  constructor() {
4979
5032
  this.loggerService = inject(TYPES.loggerService);
4980
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
4981
- this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
4982
- this.frameGlobalService = inject(TYPES.frameGlobalService);
5033
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5034
+ this.exchangeCoreService = inject(TYPES.exchangeCoreService);
5035
+ this.frameCoreService = inject(TYPES.frameCoreService);
4983
5036
  this.methodContextService = inject(TYPES.methodContextService);
4984
5037
  }
4985
5038
  /**
@@ -5001,7 +5054,7 @@ class BacktestLogicPrivateService {
5001
5054
  symbol,
5002
5055
  });
5003
5056
  const backtestStartTime = performance.now();
5004
- const timeframes = await this.frameGlobalService.getTimeframe(symbol, this.methodContextService.context.frameName);
5057
+ const timeframes = await this.frameCoreService.getTimeframe(symbol, this.methodContextService.context.frameName);
5005
5058
  const totalFrames = timeframes.length;
5006
5059
  let i = 0;
5007
5060
  let previousEventTimestamp = null;
@@ -5020,7 +5073,7 @@ class BacktestLogicPrivateService {
5020
5073
  });
5021
5074
  }
5022
5075
  // Check if strategy should stop before processing next frame
5023
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5076
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5024
5077
  this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
5025
5078
  symbol,
5026
5079
  when: when.toISOString(),
@@ -5031,7 +5084,7 @@ class BacktestLogicPrivateService {
5031
5084
  }
5032
5085
  let result;
5033
5086
  try {
5034
- result = await this.strategyGlobalService.tick(symbol, when, true);
5087
+ result = await this.strategyCoreService.tick(symbol, when, true);
5035
5088
  }
5036
5089
  catch (error) {
5037
5090
  console.warn(`backtestLogicPrivateService tick failed, skipping timeframe when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5045,7 +5098,7 @@ class BacktestLogicPrivateService {
5045
5098
  continue;
5046
5099
  }
5047
5100
  // Check if strategy should stop when idle (no active signal)
5048
- if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5101
+ if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5049
5102
  this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
5050
5103
  symbol,
5051
5104
  when: when.toISOString(),
@@ -5075,7 +5128,7 @@ class BacktestLogicPrivateService {
5075
5128
  const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
5076
5129
  let candles;
5077
5130
  try {
5078
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5131
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5079
5132
  }
5080
5133
  catch (error) {
5081
5134
  console.warn(`backtestLogicPrivateService getNextCandles failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5104,7 +5157,7 @@ class BacktestLogicPrivateService {
5104
5157
  // и если активируется - продолжит с TP/SL мониторингом
5105
5158
  let backtestResult;
5106
5159
  try {
5107
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5160
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5108
5161
  }
5109
5162
  catch (error) {
5110
5163
  console.warn(`backtestLogicPrivateService backtest failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5147,7 +5200,7 @@ class BacktestLogicPrivateService {
5147
5200
  }
5148
5201
  yield backtestResult;
5149
5202
  // Check if strategy should stop after signal is closed
5150
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5203
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5151
5204
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
5152
5205
  symbol,
5153
5206
  signalId: backtestResult.signal.id,
@@ -5174,7 +5227,7 @@ class BacktestLogicPrivateService {
5174
5227
  const totalCandles = signal.minuteEstimatedTime + bufferMinutes;
5175
5228
  let candles;
5176
5229
  try {
5177
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5230
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5178
5231
  }
5179
5232
  catch (error) {
5180
5233
  console.warn(`backtestLogicPrivateService getNextCandles failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5201,7 +5254,7 @@ class BacktestLogicPrivateService {
5201
5254
  // Вызываем backtest - всегда возвращает closed
5202
5255
  let backtestResult;
5203
5256
  try {
5204
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5257
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5205
5258
  }
5206
5259
  catch (error) {
5207
5260
  console.warn(`backtestLogicPrivateService backtest failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5240,7 +5293,7 @@ class BacktestLogicPrivateService {
5240
5293
  }
5241
5294
  yield backtestResult;
5242
5295
  // Check if strategy should stop after signal is closed
5243
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5296
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5244
5297
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
5245
5298
  symbol,
5246
5299
  signalId: backtestResult.signal.id,
@@ -5314,7 +5367,7 @@ const TICK_TTL = 1 * 60 * 1000 + 1;
5314
5367
  class LiveLogicPrivateService {
5315
5368
  constructor() {
5316
5369
  this.loggerService = inject(TYPES.loggerService);
5317
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
5370
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5318
5371
  this.methodContextService = inject(TYPES.methodContextService);
5319
5372
  }
5320
5373
  /**
@@ -5349,7 +5402,7 @@ class LiveLogicPrivateService {
5349
5402
  const when = new Date();
5350
5403
  let result;
5351
5404
  try {
5352
- result = await this.strategyGlobalService.tick(symbol, when, false);
5405
+ result = await this.strategyCoreService.tick(symbol, when, false);
5353
5406
  }
5354
5407
  catch (error) {
5355
5408
  console.warn(`backtestLogicPrivateService tick failed when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5383,7 +5436,7 @@ class LiveLogicPrivateService {
5383
5436
  previousEventTimestamp = currentTimestamp;
5384
5437
  // Check if strategy should stop when idle (no active signal)
5385
5438
  if (result.action === "idle") {
5386
- if (await functoolsKit.and(Promise.resolve(true), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5439
+ if (await functoolsKit.and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5387
5440
  this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
5388
5441
  symbol,
5389
5442
  when: when.toISOString(),
@@ -5405,7 +5458,7 @@ class LiveLogicPrivateService {
5405
5458
  yield result;
5406
5459
  // Check if strategy should stop after signal is closed
5407
5460
  if (result.action === "closed") {
5408
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5461
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5409
5462
  this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
5410
5463
  symbol,
5411
5464
  signalId: result.signal.id,
@@ -6496,14 +6549,11 @@ let ReportStorage$3 = class ReportStorage {
6496
6549
  }
6497
6550
  }
6498
6551
  /**
6499
- * Updates or adds an active event to the storage.
6500
- * Replaces the previous event with the same signalId.
6552
+ * Adds an active event to the storage.
6501
6553
  *
6502
6554
  * @param data - Active tick result
6503
6555
  */
6504
6556
  addActiveEvent(data) {
6505
- // Find existing event with the same signalId
6506
- const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
6507
6557
  const newEvent = {
6508
6558
  timestamp: Date.now(),
6509
6559
  action: "active",
@@ -6518,29 +6568,20 @@ let ReportStorage$3 = class ReportStorage {
6518
6568
  percentTp: data.percentTp,
6519
6569
  percentSl: data.percentSl,
6520
6570
  };
6521
- // Replace existing event or add new one
6522
- if (existingIndex !== -1) {
6523
- this._eventList[existingIndex] = newEvent;
6524
- }
6525
- else {
6526
- this._eventList.push(newEvent);
6527
- // Trim queue if exceeded MAX_EVENTS
6528
- if (this._eventList.length > MAX_EVENTS$3) {
6529
- this._eventList.shift();
6530
- }
6571
+ this._eventList.push(newEvent);
6572
+ // Trim queue if exceeded MAX_EVENTS
6573
+ if (this._eventList.length > MAX_EVENTS$3) {
6574
+ this._eventList.shift();
6531
6575
  }
6532
6576
  }
6533
6577
  /**
6534
- * Updates or adds a closed event to the storage.
6535
- * Replaces the previous event with the same signalId.
6578
+ * Adds a closed event to the storage.
6536
6579
  *
6537
6580
  * @param data - Closed tick result
6538
6581
  */
6539
6582
  addClosedEvent(data) {
6540
6583
  const durationMs = data.closeTimestamp - data.signal.pendingAt;
6541
6584
  const durationMin = Math.round(durationMs / 60000);
6542
- // Find existing event with the same signalId
6543
- const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
6544
6585
  const newEvent = {
6545
6586
  timestamp: data.closeTimestamp,
6546
6587
  action: "closed",
@@ -6556,16 +6597,10 @@ let ReportStorage$3 = class ReportStorage {
6556
6597
  closeReason: data.closeReason,
6557
6598
  duration: durationMin,
6558
6599
  };
6559
- // Replace existing event or add new one
6560
- if (existingIndex !== -1) {
6561
- this._eventList[existingIndex] = newEvent;
6562
- }
6563
- else {
6564
- this._eventList.push(newEvent);
6565
- // Trim queue if exceeded MAX_EVENTS
6566
- if (this._eventList.length > MAX_EVENTS$3) {
6567
- this._eventList.shift();
6568
- }
6600
+ this._eventList.push(newEvent);
6601
+ // Trim queue if exceeded MAX_EVENTS
6602
+ if (this._eventList.length > MAX_EVENTS$3) {
6603
+ this._eventList.shift();
6569
6604
  }
6570
6605
  }
6571
6606
  /**
@@ -7000,16 +7035,13 @@ let ReportStorage$2 = class ReportStorage {
7000
7035
  }
7001
7036
  }
7002
7037
  /**
7003
- * Updates or adds a cancelled event to the storage.
7004
- * Replaces the previous event with the same signalId.
7038
+ * Adds a cancelled event to the storage.
7005
7039
  *
7006
7040
  * @param data - Cancelled tick result
7007
7041
  */
7008
7042
  addCancelledEvent(data) {
7009
7043
  const durationMs = data.closeTimestamp - data.signal.scheduledAt;
7010
7044
  const durationMin = Math.round(durationMs / 60000);
7011
- // Find existing event with the same signalId
7012
- const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
7013
7045
  const newEvent = {
7014
7046
  timestamp: data.closeTimestamp,
7015
7047
  action: "cancelled",
@@ -7024,16 +7056,10 @@ let ReportStorage$2 = class ReportStorage {
7024
7056
  closeTimestamp: data.closeTimestamp,
7025
7057
  duration: durationMin,
7026
7058
  };
7027
- // Replace existing event or add new one
7028
- if (existingIndex !== -1) {
7029
- this._eventList[existingIndex] = newEvent;
7030
- }
7031
- else {
7032
- this._eventList.push(newEvent);
7033
- // Trim queue if exceeded MAX_EVENTS
7034
- if (this._eventList.length > MAX_EVENTS$2) {
7035
- this._eventList.shift();
7036
- }
7059
+ this._eventList.push(newEvent);
7060
+ // Trim queue if exceeded MAX_EVENTS
7061
+ if (this._eventList.length > MAX_EVENTS$2) {
7062
+ this._eventList.shift();
7037
7063
  }
7038
7064
  }
7039
7065
  /**
@@ -11841,9 +11867,11 @@ class ConfigValidationService {
11841
11867
  provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
11842
11868
  }
11843
11869
  {
11844
- provide(TYPES.exchangeGlobalService, () => new ExchangeGlobalService());
11845
- provide(TYPES.strategyGlobalService, () => new StrategyGlobalService());
11846
- provide(TYPES.frameGlobalService, () => new FrameGlobalService());
11870
+ provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
11871
+ provide(TYPES.strategyCoreService, () => new StrategyCoreService());
11872
+ provide(TYPES.frameCoreService, () => new FrameCoreService());
11873
+ }
11874
+ {
11847
11875
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
11848
11876
  provide(TYPES.riskGlobalService, () => new RiskGlobalService());
11849
11877
  provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
@@ -11913,10 +11941,12 @@ const schemaServices = {
11913
11941
  riskSchemaService: inject(TYPES.riskSchemaService),
11914
11942
  optimizerSchemaService: inject(TYPES.optimizerSchemaService),
11915
11943
  };
11944
+ const coreServices = {
11945
+ exchangeCoreService: inject(TYPES.exchangeCoreService),
11946
+ strategyCoreService: inject(TYPES.strategyCoreService),
11947
+ frameCoreService: inject(TYPES.frameCoreService),
11948
+ };
11916
11949
  const globalServices = {
11917
- exchangeGlobalService: inject(TYPES.exchangeGlobalService),
11918
- strategyGlobalService: inject(TYPES.strategyGlobalService),
11919
- frameGlobalService: inject(TYPES.frameGlobalService),
11920
11950
  sizingGlobalService: inject(TYPES.sizingGlobalService),
11921
11951
  riskGlobalService: inject(TYPES.riskGlobalService),
11922
11952
  optimizerGlobalService: inject(TYPES.optimizerGlobalService),
@@ -11965,6 +11995,7 @@ const backtest = {
11965
11995
  ...contextServices,
11966
11996
  ...connectionServices,
11967
11997
  ...schemaServices,
11998
+ ...coreServices,
11968
11999
  ...globalServices,
11969
12000
  ...commandServices,
11970
12001
  ...logicPrivateServices,
@@ -11999,6 +12030,7 @@ function setLogger(logger) {
11999
12030
  /**
12000
12031
  * Sets global configuration parameters for the framework.
12001
12032
  * @param config - Partial configuration object to override default settings
12033
+ * @param _unsafe - Skip config validations - required for testbed
12002
12034
  *
12003
12035
  * @example
12004
12036
  * ```typescript
@@ -13861,7 +13893,7 @@ class BacktestInstance {
13861
13893
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
13862
13894
  }
13863
13895
  {
13864
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
13896
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
13865
13897
  }
13866
13898
  {
13867
13899
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -13896,8 +13928,8 @@ class BacktestInstance {
13896
13928
  });
13897
13929
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13898
13930
  return () => {
13899
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
13900
- backtest$1.strategyGlobalService
13931
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, true);
13932
+ backtest$1.strategyCoreService
13901
13933
  .getPendingSignal(symbol, context.strategyName)
13902
13934
  .then(async (pendingSignal) => {
13903
13935
  if (pendingSignal) {
@@ -13938,7 +13970,7 @@ class BacktestInstance {
13938
13970
  symbol,
13939
13971
  strategyName,
13940
13972
  });
13941
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
13973
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
13942
13974
  };
13943
13975
  /**
13944
13976
  * Gets statistical data from all closed signals for a symbol-strategy pair.
@@ -14353,7 +14385,7 @@ class LiveInstance {
14353
14385
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
14354
14386
  }
14355
14387
  {
14356
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
14388
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
14357
14389
  }
14358
14390
  {
14359
14391
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -14388,8 +14420,8 @@ class LiveInstance {
14388
14420
  });
14389
14421
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14390
14422
  return () => {
14391
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
14392
- backtest$1.strategyGlobalService
14423
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, false);
14424
+ backtest$1.strategyCoreService
14393
14425
  .getPendingSignal(symbol, context.strategyName)
14394
14426
  .then(async (pendingSignal) => {
14395
14427
  if (pendingSignal) {
@@ -14430,7 +14462,7 @@ class LiveInstance {
14430
14462
  symbol,
14431
14463
  strategyName,
14432
14464
  });
14433
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
14465
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, false);
14434
14466
  };
14435
14467
  /**
14436
14468
  * Gets statistical data from all live trading events for a symbol-strategy pair.
@@ -15111,7 +15143,7 @@ class WalkerInstance {
15111
15143
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
15112
15144
  }
15113
15145
  {
15114
- backtest$1.strategyGlobalService.clear({ symbol, strategyName });
15146
+ backtest$1.strategyCoreService.clear({ symbol, strategyName });
15115
15147
  }
15116
15148
  {
15117
15149
  const { riskName } = backtest$1.strategySchemaService.get(strategyName);
@@ -15151,7 +15183,7 @@ class WalkerInstance {
15151
15183
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
15152
15184
  return () => {
15153
15185
  for (const strategyName of walkerSchema.strategies) {
15154
- backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15186
+ backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15155
15187
  walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
15156
15188
  }
15157
15189
  if (!this._isDone) {
@@ -15197,7 +15229,7 @@ class WalkerInstance {
15197
15229
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
15198
15230
  for (const strategyName of walkerSchema.strategies) {
15199
15231
  await walkerStopSubject.next({ symbol, strategyName, walkerName });
15200
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15232
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15201
15233
  }
15202
15234
  };
15203
15235
  /**