backtest-kit 1.5.13 → 1.5.14

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,
@@ -2001,15 +2004,6 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
2001
2004
  self._lastSignalTimestamp = currentTime;
2002
2005
  }
2003
2006
  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
2007
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
2014
2008
  const signal = await Promise.race([
2015
2009
  self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
@@ -2024,6 +2018,16 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
2024
2018
  if (self._isStopped) {
2025
2019
  return null;
2026
2020
  }
2021
+ if (await functoolsKit.not(self.params.risk.checkSignal({
2022
+ pendingSignal: signal,
2023
+ symbol: self.params.execution.context.symbol,
2024
+ strategyName: self.params.method.context.strategyName,
2025
+ exchangeName: self.params.method.context.exchangeName,
2026
+ currentPrice,
2027
+ timestamp: currentTime,
2028
+ }))) {
2029
+ return null;
2030
+ }
2027
2031
  // Если priceOpen указан - проверяем нужно ли ждать активации или открыть сразу
2028
2032
  if (signal.priceOpen !== undefined) {
2029
2033
  // КРИТИЧЕСКАЯ ПРОВЕРКА: достигнут ли priceOpen?
@@ -2255,6 +2259,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
2255
2259
  });
2256
2260
  if (await functoolsKit.not(self.params.risk.checkSignal({
2257
2261
  symbol: self.params.execution.context.symbol,
2262
+ pendingSignal: scheduled,
2258
2263
  strategyName: self.params.method.context.strategyName,
2259
2264
  exchangeName: self.params.method.context.exchangeName,
2260
2265
  currentPrice: scheduled.priceOpen,
@@ -2338,6 +2343,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
2338
2343
  };
2339
2344
  const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
2340
2345
  if (await functoolsKit.not(self.params.risk.checkSignal({
2346
+ pendingSignal: signal,
2341
2347
  symbol: self.params.execution.context.symbol,
2342
2348
  strategyName: self.params.method.context.strategyName,
2343
2349
  exchangeName: self.params.method.context.exchangeName,
@@ -2564,6 +2570,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
2564
2570
  pendingAt: activationTime,
2565
2571
  });
2566
2572
  if (await functoolsKit.not(self.params.risk.checkSignal({
2573
+ pendingSignal: scheduled,
2567
2574
  symbol: self.params.execution.context.symbol,
2568
2575
  strategyName: self.params.method.context.strategyName,
2569
2576
  exchangeName: self.params.method.context.exchangeName,
@@ -4025,7 +4032,7 @@ class RiskConnectionService {
4025
4032
  }
4026
4033
  }
4027
4034
 
4028
- const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4035
+ const METHOD_NAME_VALIDATE$1 = "exchangeCoreService validate";
4029
4036
  /**
4030
4037
  * Global service for exchange operations with execution context injection.
4031
4038
  *
@@ -4034,7 +4041,7 @@ const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4034
4041
  *
4035
4042
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4036
4043
  */
4037
- class ExchangeGlobalService {
4044
+ class ExchangeCoreService {
4038
4045
  constructor() {
4039
4046
  this.loggerService = inject(TYPES.loggerService);
4040
4047
  this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
@@ -4064,13 +4071,16 @@ class ExchangeGlobalService {
4064
4071
  * @returns Promise resolving to array of candles
4065
4072
  */
4066
4073
  this.getCandles = async (symbol, interval, limit, when, backtest) => {
4067
- this.loggerService.log("exchangeGlobalService getCandles", {
4074
+ this.loggerService.log("exchangeCoreService getCandles", {
4068
4075
  symbol,
4069
4076
  interval,
4070
4077
  limit,
4071
4078
  when,
4072
4079
  backtest,
4073
4080
  });
4081
+ if (!MethodContextService.hasContext()) {
4082
+ throw new Error("exchangeCoreService getCandles requires a method context");
4083
+ }
4074
4084
  await this.validate(this.methodContextService.context.exchangeName);
4075
4085
  return await ExecutionContextService.runInContext(async () => {
4076
4086
  return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
@@ -4091,13 +4101,16 @@ class ExchangeGlobalService {
4091
4101
  * @returns Promise resolving to array of future candles
4092
4102
  */
4093
4103
  this.getNextCandles = async (symbol, interval, limit, when, backtest) => {
4094
- this.loggerService.log("exchangeGlobalService getNextCandles", {
4104
+ this.loggerService.log("exchangeCoreService getNextCandles", {
4095
4105
  symbol,
4096
4106
  interval,
4097
4107
  limit,
4098
4108
  when,
4099
4109
  backtest,
4100
4110
  });
4111
+ if (!MethodContextService.hasContext()) {
4112
+ throw new Error("exchangeCoreService getNextCandles requires a method context");
4113
+ }
4101
4114
  await this.validate(this.methodContextService.context.exchangeName);
4102
4115
  return await ExecutionContextService.runInContext(async () => {
4103
4116
  return await this.exchangeConnectionService.getNextCandles(symbol, interval, limit);
@@ -4116,11 +4129,14 @@ class ExchangeGlobalService {
4116
4129
  * @returns Promise resolving to VWAP price
4117
4130
  */
4118
4131
  this.getAveragePrice = async (symbol, when, backtest) => {
4119
- this.loggerService.log("exchangeGlobalService getAveragePrice", {
4132
+ this.loggerService.log("exchangeCoreService getAveragePrice", {
4120
4133
  symbol,
4121
4134
  when,
4122
4135
  backtest,
4123
4136
  });
4137
+ if (!MethodContextService.hasContext()) {
4138
+ throw new Error("exchangeCoreService getAveragePrice requires a method context");
4139
+ }
4124
4140
  await this.validate(this.methodContextService.context.exchangeName);
4125
4141
  return await ExecutionContextService.runInContext(async () => {
4126
4142
  return await this.exchangeConnectionService.getAveragePrice(symbol);
@@ -4140,12 +4156,15 @@ class ExchangeGlobalService {
4140
4156
  * @returns Promise resolving to formatted price string
4141
4157
  */
4142
4158
  this.formatPrice = async (symbol, price, when, backtest) => {
4143
- this.loggerService.log("exchangeGlobalService formatPrice", {
4159
+ this.loggerService.log("exchangeCoreService formatPrice", {
4144
4160
  symbol,
4145
4161
  price,
4146
4162
  when,
4147
4163
  backtest,
4148
4164
  });
4165
+ if (!MethodContextService.hasContext()) {
4166
+ throw new Error("exchangeCoreService formatPrice requires a method context");
4167
+ }
4149
4168
  await this.validate(this.methodContextService.context.exchangeName);
4150
4169
  return await ExecutionContextService.runInContext(async () => {
4151
4170
  return await this.exchangeConnectionService.formatPrice(symbol, price);
@@ -4165,12 +4184,15 @@ class ExchangeGlobalService {
4165
4184
  * @returns Promise resolving to formatted quantity string
4166
4185
  */
4167
4186
  this.formatQuantity = async (symbol, quantity, when, backtest) => {
4168
- this.loggerService.log("exchangeGlobalService formatQuantity", {
4187
+ this.loggerService.log("exchangeCoreService formatQuantity", {
4169
4188
  symbol,
4170
4189
  quantity,
4171
4190
  when,
4172
4191
  backtest,
4173
4192
  });
4193
+ if (!MethodContextService.hasContext()) {
4194
+ throw new Error("exchangeCoreService formatQuantity requires a method context");
4195
+ }
4174
4196
  await this.validate(this.methodContextService.context.exchangeName);
4175
4197
  return await ExecutionContextService.runInContext(async () => {
4176
4198
  return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
@@ -4183,7 +4205,7 @@ class ExchangeGlobalService {
4183
4205
  }
4184
4206
  }
4185
4207
 
4186
- const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4208
+ const METHOD_NAME_VALIDATE = "strategyCoreService validate";
4187
4209
  /**
4188
4210
  * Global service for strategy operations with execution context injection.
4189
4211
  *
@@ -4192,7 +4214,7 @@ const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4192
4214
  *
4193
4215
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4194
4216
  */
4195
- class StrategyGlobalService {
4217
+ class StrategyCoreService {
4196
4218
  constructor() {
4197
4219
  this.loggerService = inject(TYPES.loggerService);
4198
4220
  this.strategyConnectionService = inject(TYPES.strategyConnectionService);
@@ -4230,10 +4252,13 @@ class StrategyGlobalService {
4230
4252
  * @returns Promise resolving to pending signal or null
4231
4253
  */
4232
4254
  this.getPendingSignal = async (symbol, strategyName) => {
4233
- this.loggerService.log("strategyGlobalService getPendingSignal", {
4255
+ this.loggerService.log("strategyCoreService getPendingSignal", {
4234
4256
  symbol,
4235
4257
  strategyName,
4236
4258
  });
4259
+ if (!MethodContextService.hasContext()) {
4260
+ throw new Error("strategyCoreService getPendingSignal requires a method context");
4261
+ }
4237
4262
  await this.validate(symbol, strategyName);
4238
4263
  return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
4239
4264
  };
@@ -4248,10 +4273,13 @@ class StrategyGlobalService {
4248
4273
  * @returns Promise resolving to true if strategy is stopped, false otherwise
4249
4274
  */
4250
4275
  this.getStopped = async (symbol, strategyName) => {
4251
- this.loggerService.log("strategyGlobalService getStopped", {
4276
+ this.loggerService.log("strategyCoreService getStopped", {
4252
4277
  symbol,
4253
4278
  strategyName,
4254
4279
  });
4280
+ if (!MethodContextService.hasContext()) {
4281
+ throw new Error("strategyCoreService getStopped requires a method context");
4282
+ }
4255
4283
  await this.validate(symbol, strategyName);
4256
4284
  return await this.strategyConnectionService.getStopped(symbol, strategyName);
4257
4285
  };
@@ -4267,11 +4295,14 @@ class StrategyGlobalService {
4267
4295
  * @returns Discriminated union of tick result (idle, opened, active, closed)
4268
4296
  */
4269
4297
  this.tick = async (symbol, when, backtest) => {
4270
- this.loggerService.log("strategyGlobalService tick", {
4298
+ this.loggerService.log("strategyCoreService tick", {
4271
4299
  symbol,
4272
4300
  when,
4273
4301
  backtest,
4274
4302
  });
4303
+ if (!MethodContextService.hasContext()) {
4304
+ throw new Error("strategyCoreService tick requires a method context");
4305
+ }
4275
4306
  const strategyName = this.methodContextService.context.strategyName;
4276
4307
  await this.validate(symbol, strategyName);
4277
4308
  return await ExecutionContextService.runInContext(async () => {
@@ -4295,12 +4326,15 @@ class StrategyGlobalService {
4295
4326
  * @returns Closed signal result with PNL
4296
4327
  */
4297
4328
  this.backtest = async (symbol, candles, when, backtest) => {
4298
- this.loggerService.log("strategyGlobalService backtest", {
4329
+ this.loggerService.log("strategyCoreService backtest", {
4299
4330
  symbol,
4300
4331
  candleCount: candles.length,
4301
4332
  when,
4302
4333
  backtest,
4303
4334
  });
4335
+ if (!MethodContextService.hasContext()) {
4336
+ throw new Error("strategyCoreService backtest requires a method context");
4337
+ }
4304
4338
  const strategyName = this.methodContextService.context.strategyName;
4305
4339
  await this.validate(symbol, strategyName);
4306
4340
  return await ExecutionContextService.runInContext(async () => {
@@ -4322,7 +4356,7 @@ class StrategyGlobalService {
4322
4356
  * @returns Promise that resolves when stop flag is set
4323
4357
  */
4324
4358
  this.stop = async (ctx, backtest) => {
4325
- this.loggerService.log("strategyGlobalService stop", {
4359
+ this.loggerService.log("strategyCoreService stop", {
4326
4360
  ctx,
4327
4361
  backtest,
4328
4362
  });
@@ -4338,7 +4372,7 @@ class StrategyGlobalService {
4338
4372
  * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
4339
4373
  */
4340
4374
  this.clear = async (ctx) => {
4341
- this.loggerService.log("strategyGlobalService clear", {
4375
+ this.loggerService.log("strategyCoreService clear", {
4342
4376
  ctx,
4343
4377
  });
4344
4378
  if (ctx) {
@@ -4349,14 +4383,14 @@ class StrategyGlobalService {
4349
4383
  }
4350
4384
  }
4351
4385
 
4352
- const METHOD_NAME_GET_TIMEFRAME = "frameGlobalService getTimeframe";
4386
+ const METHOD_NAME_GET_TIMEFRAME = "frameCoreService getTimeframe";
4353
4387
  /**
4354
4388
  * Global service for frame operations.
4355
4389
  *
4356
4390
  * Wraps FrameConnectionService for timeframe generation.
4357
4391
  * Used internally by BacktestLogicPrivateService.
4358
4392
  */
4359
- class FrameGlobalService {
4393
+ class FrameCoreService {
4360
4394
  constructor() {
4361
4395
  this.loggerService = inject(TYPES.loggerService);
4362
4396
  this.frameConnectionService = inject(TYPES.frameConnectionService);
@@ -4372,6 +4406,9 @@ class FrameGlobalService {
4372
4406
  frameName,
4373
4407
  symbol,
4374
4408
  });
4409
+ if (!MethodContextService.hasContext()) {
4410
+ throw new Error("frameCoreService getTimeframe requires a method context");
4411
+ }
4375
4412
  this.frameValidationService.validate(frameName, METHOD_NAME_GET_TIMEFRAME);
4376
4413
  return await this.frameConnectionService.getTimeframe(symbol, frameName);
4377
4414
  };
@@ -4977,9 +5014,9 @@ class WalkerSchemaService {
4977
5014
  class BacktestLogicPrivateService {
4978
5015
  constructor() {
4979
5016
  this.loggerService = inject(TYPES.loggerService);
4980
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
4981
- this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
4982
- this.frameGlobalService = inject(TYPES.frameGlobalService);
5017
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5018
+ this.exchangeCoreService = inject(TYPES.exchangeCoreService);
5019
+ this.frameCoreService = inject(TYPES.frameCoreService);
4983
5020
  this.methodContextService = inject(TYPES.methodContextService);
4984
5021
  }
4985
5022
  /**
@@ -5001,7 +5038,7 @@ class BacktestLogicPrivateService {
5001
5038
  symbol,
5002
5039
  });
5003
5040
  const backtestStartTime = performance.now();
5004
- const timeframes = await this.frameGlobalService.getTimeframe(symbol, this.methodContextService.context.frameName);
5041
+ const timeframes = await this.frameCoreService.getTimeframe(symbol, this.methodContextService.context.frameName);
5005
5042
  const totalFrames = timeframes.length;
5006
5043
  let i = 0;
5007
5044
  let previousEventTimestamp = null;
@@ -5020,7 +5057,7 @@ class BacktestLogicPrivateService {
5020
5057
  });
5021
5058
  }
5022
5059
  // Check if strategy should stop before processing next frame
5023
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5060
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5024
5061
  this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
5025
5062
  symbol,
5026
5063
  when: when.toISOString(),
@@ -5031,7 +5068,7 @@ class BacktestLogicPrivateService {
5031
5068
  }
5032
5069
  let result;
5033
5070
  try {
5034
- result = await this.strategyGlobalService.tick(symbol, when, true);
5071
+ result = await this.strategyCoreService.tick(symbol, when, true);
5035
5072
  }
5036
5073
  catch (error) {
5037
5074
  console.warn(`backtestLogicPrivateService tick failed, skipping timeframe when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5045,7 +5082,7 @@ class BacktestLogicPrivateService {
5045
5082
  continue;
5046
5083
  }
5047
5084
  // 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))) {
5085
+ if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5049
5086
  this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
5050
5087
  symbol,
5051
5088
  when: when.toISOString(),
@@ -5075,7 +5112,7 @@ class BacktestLogicPrivateService {
5075
5112
  const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
5076
5113
  let candles;
5077
5114
  try {
5078
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5115
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5079
5116
  }
5080
5117
  catch (error) {
5081
5118
  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 +5141,7 @@ class BacktestLogicPrivateService {
5104
5141
  // и если активируется - продолжит с TP/SL мониторингом
5105
5142
  let backtestResult;
5106
5143
  try {
5107
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5144
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5108
5145
  }
5109
5146
  catch (error) {
5110
5147
  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 +5184,7 @@ class BacktestLogicPrivateService {
5147
5184
  }
5148
5185
  yield backtestResult;
5149
5186
  // Check if strategy should stop after signal is closed
5150
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5187
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5151
5188
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
5152
5189
  symbol,
5153
5190
  signalId: backtestResult.signal.id,
@@ -5174,7 +5211,7 @@ class BacktestLogicPrivateService {
5174
5211
  const totalCandles = signal.minuteEstimatedTime + bufferMinutes;
5175
5212
  let candles;
5176
5213
  try {
5177
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5214
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5178
5215
  }
5179
5216
  catch (error) {
5180
5217
  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 +5238,7 @@ class BacktestLogicPrivateService {
5201
5238
  // Вызываем backtest - всегда возвращает closed
5202
5239
  let backtestResult;
5203
5240
  try {
5204
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5241
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5205
5242
  }
5206
5243
  catch (error) {
5207
5244
  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 +5277,7 @@ class BacktestLogicPrivateService {
5240
5277
  }
5241
5278
  yield backtestResult;
5242
5279
  // Check if strategy should stop after signal is closed
5243
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5280
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5244
5281
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
5245
5282
  symbol,
5246
5283
  signalId: backtestResult.signal.id,
@@ -5314,7 +5351,7 @@ const TICK_TTL = 1 * 60 * 1000 + 1;
5314
5351
  class LiveLogicPrivateService {
5315
5352
  constructor() {
5316
5353
  this.loggerService = inject(TYPES.loggerService);
5317
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
5354
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5318
5355
  this.methodContextService = inject(TYPES.methodContextService);
5319
5356
  }
5320
5357
  /**
@@ -5349,7 +5386,7 @@ class LiveLogicPrivateService {
5349
5386
  const when = new Date();
5350
5387
  let result;
5351
5388
  try {
5352
- result = await this.strategyGlobalService.tick(symbol, when, false);
5389
+ result = await this.strategyCoreService.tick(symbol, when, false);
5353
5390
  }
5354
5391
  catch (error) {
5355
5392
  console.warn(`backtestLogicPrivateService tick failed when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5383,7 +5420,7 @@ class LiveLogicPrivateService {
5383
5420
  previousEventTimestamp = currentTimestamp;
5384
5421
  // Check if strategy should stop when idle (no active signal)
5385
5422
  if (result.action === "idle") {
5386
- if (await functoolsKit.and(Promise.resolve(true), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5423
+ if (await functoolsKit.and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5387
5424
  this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
5388
5425
  symbol,
5389
5426
  when: when.toISOString(),
@@ -5405,7 +5442,7 @@ class LiveLogicPrivateService {
5405
5442
  yield result;
5406
5443
  // Check if strategy should stop after signal is closed
5407
5444
  if (result.action === "closed") {
5408
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5445
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5409
5446
  this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
5410
5447
  symbol,
5411
5448
  signalId: result.signal.id,
@@ -11841,9 +11878,11 @@ class ConfigValidationService {
11841
11878
  provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
11842
11879
  }
11843
11880
  {
11844
- provide(TYPES.exchangeGlobalService, () => new ExchangeGlobalService());
11845
- provide(TYPES.strategyGlobalService, () => new StrategyGlobalService());
11846
- provide(TYPES.frameGlobalService, () => new FrameGlobalService());
11881
+ provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
11882
+ provide(TYPES.strategyCoreService, () => new StrategyCoreService());
11883
+ provide(TYPES.frameCoreService, () => new FrameCoreService());
11884
+ }
11885
+ {
11847
11886
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
11848
11887
  provide(TYPES.riskGlobalService, () => new RiskGlobalService());
11849
11888
  provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
@@ -11913,10 +11952,12 @@ const schemaServices = {
11913
11952
  riskSchemaService: inject(TYPES.riskSchemaService),
11914
11953
  optimizerSchemaService: inject(TYPES.optimizerSchemaService),
11915
11954
  };
11955
+ const coreServices = {
11956
+ exchangeCoreService: inject(TYPES.exchangeCoreService),
11957
+ strategyCoreService: inject(TYPES.strategyCoreService),
11958
+ frameCoreService: inject(TYPES.frameCoreService),
11959
+ };
11916
11960
  const globalServices = {
11917
- exchangeGlobalService: inject(TYPES.exchangeGlobalService),
11918
- strategyGlobalService: inject(TYPES.strategyGlobalService),
11919
- frameGlobalService: inject(TYPES.frameGlobalService),
11920
11961
  sizingGlobalService: inject(TYPES.sizingGlobalService),
11921
11962
  riskGlobalService: inject(TYPES.riskGlobalService),
11922
11963
  optimizerGlobalService: inject(TYPES.optimizerGlobalService),
@@ -11965,6 +12006,7 @@ const backtest = {
11965
12006
  ...contextServices,
11966
12007
  ...connectionServices,
11967
12008
  ...schemaServices,
12009
+ ...coreServices,
11968
12010
  ...globalServices,
11969
12011
  ...commandServices,
11970
12012
  ...logicPrivateServices,
@@ -11999,6 +12041,7 @@ function setLogger(logger) {
11999
12041
  /**
12000
12042
  * Sets global configuration parameters for the framework.
12001
12043
  * @param config - Partial configuration object to override default settings
12044
+ * @param _unsafe - Skip config validations - required for testbed
12002
12045
  *
12003
12046
  * @example
12004
12047
  * ```typescript
@@ -13861,7 +13904,7 @@ class BacktestInstance {
13861
13904
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
13862
13905
  }
13863
13906
  {
13864
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
13907
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
13865
13908
  }
13866
13909
  {
13867
13910
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -13896,8 +13939,8 @@ class BacktestInstance {
13896
13939
  });
13897
13940
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
13898
13941
  return () => {
13899
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
13900
- backtest$1.strategyGlobalService
13942
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, true);
13943
+ backtest$1.strategyCoreService
13901
13944
  .getPendingSignal(symbol, context.strategyName)
13902
13945
  .then(async (pendingSignal) => {
13903
13946
  if (pendingSignal) {
@@ -13938,7 +13981,7 @@ class BacktestInstance {
13938
13981
  symbol,
13939
13982
  strategyName,
13940
13983
  });
13941
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
13984
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
13942
13985
  };
13943
13986
  /**
13944
13987
  * Gets statistical data from all closed signals for a symbol-strategy pair.
@@ -14353,7 +14396,7 @@ class LiveInstance {
14353
14396
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
14354
14397
  }
14355
14398
  {
14356
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
14399
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
14357
14400
  }
14358
14401
  {
14359
14402
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -14388,8 +14431,8 @@ class LiveInstance {
14388
14431
  });
14389
14432
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14390
14433
  return () => {
14391
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
14392
- backtest$1.strategyGlobalService
14434
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, false);
14435
+ backtest$1.strategyCoreService
14393
14436
  .getPendingSignal(symbol, context.strategyName)
14394
14437
  .then(async (pendingSignal) => {
14395
14438
  if (pendingSignal) {
@@ -14430,7 +14473,7 @@ class LiveInstance {
14430
14473
  symbol,
14431
14474
  strategyName,
14432
14475
  });
14433
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
14476
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, false);
14434
14477
  };
14435
14478
  /**
14436
14479
  * Gets statistical data from all live trading events for a symbol-strategy pair.
@@ -15111,7 +15154,7 @@ class WalkerInstance {
15111
15154
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
15112
15155
  }
15113
15156
  {
15114
- backtest$1.strategyGlobalService.clear({ symbol, strategyName });
15157
+ backtest$1.strategyCoreService.clear({ symbol, strategyName });
15115
15158
  }
15116
15159
  {
15117
15160
  const { riskName } = backtest$1.strategySchemaService.get(strategyName);
@@ -15151,7 +15194,7 @@ class WalkerInstance {
15151
15194
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
15152
15195
  return () => {
15153
15196
  for (const strategyName of walkerSchema.strategies) {
15154
- backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15197
+ backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15155
15198
  walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
15156
15199
  }
15157
15200
  if (!this._isDone) {
@@ -15197,7 +15240,7 @@ class WalkerInstance {
15197
15240
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
15198
15241
  for (const strategyName of walkerSchema.strategies) {
15199
15242
  await walkerStopSubject.next({ symbol, strategyName, walkerName });
15200
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15243
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15201
15244
  }
15202
15245
  };
15203
15246
  /**
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,
@@ -1999,15 +2002,6 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
1999
2002
  self._lastSignalTimestamp = currentTime;
2000
2003
  }
2001
2004
  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
2005
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
2012
2006
  const signal = await Promise.race([
2013
2007
  self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
@@ -2022,6 +2016,16 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
2022
2016
  if (self._isStopped) {
2023
2017
  return null;
2024
2018
  }
2019
+ if (await not(self.params.risk.checkSignal({
2020
+ pendingSignal: signal,
2021
+ symbol: self.params.execution.context.symbol,
2022
+ strategyName: self.params.method.context.strategyName,
2023
+ exchangeName: self.params.method.context.exchangeName,
2024
+ currentPrice,
2025
+ timestamp: currentTime,
2026
+ }))) {
2027
+ return null;
2028
+ }
2025
2029
  // Если priceOpen указан - проверяем нужно ли ждать активации или открыть сразу
2026
2030
  if (signal.priceOpen !== undefined) {
2027
2031
  // КРИТИЧЕСКАЯ ПРОВЕРКА: достигнут ли priceOpen?
@@ -2253,6 +2257,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
2253
2257
  });
2254
2258
  if (await not(self.params.risk.checkSignal({
2255
2259
  symbol: self.params.execution.context.symbol,
2260
+ pendingSignal: scheduled,
2256
2261
  strategyName: self.params.method.context.strategyName,
2257
2262
  exchangeName: self.params.method.context.exchangeName,
2258
2263
  currentPrice: scheduled.priceOpen,
@@ -2336,6 +2341,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
2336
2341
  };
2337
2342
  const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
2338
2343
  if (await not(self.params.risk.checkSignal({
2344
+ pendingSignal: signal,
2339
2345
  symbol: self.params.execution.context.symbol,
2340
2346
  strategyName: self.params.method.context.strategyName,
2341
2347
  exchangeName: self.params.method.context.exchangeName,
@@ -2562,6 +2568,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
2562
2568
  pendingAt: activationTime,
2563
2569
  });
2564
2570
  if (await not(self.params.risk.checkSignal({
2571
+ pendingSignal: scheduled,
2565
2572
  symbol: self.params.execution.context.symbol,
2566
2573
  strategyName: self.params.method.context.strategyName,
2567
2574
  exchangeName: self.params.method.context.exchangeName,
@@ -4023,7 +4030,7 @@ class RiskConnectionService {
4023
4030
  }
4024
4031
  }
4025
4032
 
4026
- const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4033
+ const METHOD_NAME_VALIDATE$1 = "exchangeCoreService validate";
4027
4034
  /**
4028
4035
  * Global service for exchange operations with execution context injection.
4029
4036
  *
@@ -4032,7 +4039,7 @@ const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
4032
4039
  *
4033
4040
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4034
4041
  */
4035
- class ExchangeGlobalService {
4042
+ class ExchangeCoreService {
4036
4043
  constructor() {
4037
4044
  this.loggerService = inject(TYPES.loggerService);
4038
4045
  this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
@@ -4062,13 +4069,16 @@ class ExchangeGlobalService {
4062
4069
  * @returns Promise resolving to array of candles
4063
4070
  */
4064
4071
  this.getCandles = async (symbol, interval, limit, when, backtest) => {
4065
- this.loggerService.log("exchangeGlobalService getCandles", {
4072
+ this.loggerService.log("exchangeCoreService getCandles", {
4066
4073
  symbol,
4067
4074
  interval,
4068
4075
  limit,
4069
4076
  when,
4070
4077
  backtest,
4071
4078
  });
4079
+ if (!MethodContextService.hasContext()) {
4080
+ throw new Error("exchangeCoreService getCandles requires a method context");
4081
+ }
4072
4082
  await this.validate(this.methodContextService.context.exchangeName);
4073
4083
  return await ExecutionContextService.runInContext(async () => {
4074
4084
  return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
@@ -4089,13 +4099,16 @@ class ExchangeGlobalService {
4089
4099
  * @returns Promise resolving to array of future candles
4090
4100
  */
4091
4101
  this.getNextCandles = async (symbol, interval, limit, when, backtest) => {
4092
- this.loggerService.log("exchangeGlobalService getNextCandles", {
4102
+ this.loggerService.log("exchangeCoreService getNextCandles", {
4093
4103
  symbol,
4094
4104
  interval,
4095
4105
  limit,
4096
4106
  when,
4097
4107
  backtest,
4098
4108
  });
4109
+ if (!MethodContextService.hasContext()) {
4110
+ throw new Error("exchangeCoreService getNextCandles requires a method context");
4111
+ }
4099
4112
  await this.validate(this.methodContextService.context.exchangeName);
4100
4113
  return await ExecutionContextService.runInContext(async () => {
4101
4114
  return await this.exchangeConnectionService.getNextCandles(symbol, interval, limit);
@@ -4114,11 +4127,14 @@ class ExchangeGlobalService {
4114
4127
  * @returns Promise resolving to VWAP price
4115
4128
  */
4116
4129
  this.getAveragePrice = async (symbol, when, backtest) => {
4117
- this.loggerService.log("exchangeGlobalService getAveragePrice", {
4130
+ this.loggerService.log("exchangeCoreService getAveragePrice", {
4118
4131
  symbol,
4119
4132
  when,
4120
4133
  backtest,
4121
4134
  });
4135
+ if (!MethodContextService.hasContext()) {
4136
+ throw new Error("exchangeCoreService getAveragePrice requires a method context");
4137
+ }
4122
4138
  await this.validate(this.methodContextService.context.exchangeName);
4123
4139
  return await ExecutionContextService.runInContext(async () => {
4124
4140
  return await this.exchangeConnectionService.getAveragePrice(symbol);
@@ -4138,12 +4154,15 @@ class ExchangeGlobalService {
4138
4154
  * @returns Promise resolving to formatted price string
4139
4155
  */
4140
4156
  this.formatPrice = async (symbol, price, when, backtest) => {
4141
- this.loggerService.log("exchangeGlobalService formatPrice", {
4157
+ this.loggerService.log("exchangeCoreService formatPrice", {
4142
4158
  symbol,
4143
4159
  price,
4144
4160
  when,
4145
4161
  backtest,
4146
4162
  });
4163
+ if (!MethodContextService.hasContext()) {
4164
+ throw new Error("exchangeCoreService formatPrice requires a method context");
4165
+ }
4147
4166
  await this.validate(this.methodContextService.context.exchangeName);
4148
4167
  return await ExecutionContextService.runInContext(async () => {
4149
4168
  return await this.exchangeConnectionService.formatPrice(symbol, price);
@@ -4163,12 +4182,15 @@ class ExchangeGlobalService {
4163
4182
  * @returns Promise resolving to formatted quantity string
4164
4183
  */
4165
4184
  this.formatQuantity = async (symbol, quantity, when, backtest) => {
4166
- this.loggerService.log("exchangeGlobalService formatQuantity", {
4185
+ this.loggerService.log("exchangeCoreService formatQuantity", {
4167
4186
  symbol,
4168
4187
  quantity,
4169
4188
  when,
4170
4189
  backtest,
4171
4190
  });
4191
+ if (!MethodContextService.hasContext()) {
4192
+ throw new Error("exchangeCoreService formatQuantity requires a method context");
4193
+ }
4172
4194
  await this.validate(this.methodContextService.context.exchangeName);
4173
4195
  return await ExecutionContextService.runInContext(async () => {
4174
4196
  return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
@@ -4181,7 +4203,7 @@ class ExchangeGlobalService {
4181
4203
  }
4182
4204
  }
4183
4205
 
4184
- const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4206
+ const METHOD_NAME_VALIDATE = "strategyCoreService validate";
4185
4207
  /**
4186
4208
  * Global service for strategy operations with execution context injection.
4187
4209
  *
@@ -4190,7 +4212,7 @@ const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
4190
4212
  *
4191
4213
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
4192
4214
  */
4193
- class StrategyGlobalService {
4215
+ class StrategyCoreService {
4194
4216
  constructor() {
4195
4217
  this.loggerService = inject(TYPES.loggerService);
4196
4218
  this.strategyConnectionService = inject(TYPES.strategyConnectionService);
@@ -4228,10 +4250,13 @@ class StrategyGlobalService {
4228
4250
  * @returns Promise resolving to pending signal or null
4229
4251
  */
4230
4252
  this.getPendingSignal = async (symbol, strategyName) => {
4231
- this.loggerService.log("strategyGlobalService getPendingSignal", {
4253
+ this.loggerService.log("strategyCoreService getPendingSignal", {
4232
4254
  symbol,
4233
4255
  strategyName,
4234
4256
  });
4257
+ if (!MethodContextService.hasContext()) {
4258
+ throw new Error("strategyCoreService getPendingSignal requires a method context");
4259
+ }
4235
4260
  await this.validate(symbol, strategyName);
4236
4261
  return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
4237
4262
  };
@@ -4246,10 +4271,13 @@ class StrategyGlobalService {
4246
4271
  * @returns Promise resolving to true if strategy is stopped, false otherwise
4247
4272
  */
4248
4273
  this.getStopped = async (symbol, strategyName) => {
4249
- this.loggerService.log("strategyGlobalService getStopped", {
4274
+ this.loggerService.log("strategyCoreService getStopped", {
4250
4275
  symbol,
4251
4276
  strategyName,
4252
4277
  });
4278
+ if (!MethodContextService.hasContext()) {
4279
+ throw new Error("strategyCoreService getStopped requires a method context");
4280
+ }
4253
4281
  await this.validate(symbol, strategyName);
4254
4282
  return await this.strategyConnectionService.getStopped(symbol, strategyName);
4255
4283
  };
@@ -4265,11 +4293,14 @@ class StrategyGlobalService {
4265
4293
  * @returns Discriminated union of tick result (idle, opened, active, closed)
4266
4294
  */
4267
4295
  this.tick = async (symbol, when, backtest) => {
4268
- this.loggerService.log("strategyGlobalService tick", {
4296
+ this.loggerService.log("strategyCoreService tick", {
4269
4297
  symbol,
4270
4298
  when,
4271
4299
  backtest,
4272
4300
  });
4301
+ if (!MethodContextService.hasContext()) {
4302
+ throw new Error("strategyCoreService tick requires a method context");
4303
+ }
4273
4304
  const strategyName = this.methodContextService.context.strategyName;
4274
4305
  await this.validate(symbol, strategyName);
4275
4306
  return await ExecutionContextService.runInContext(async () => {
@@ -4293,12 +4324,15 @@ class StrategyGlobalService {
4293
4324
  * @returns Closed signal result with PNL
4294
4325
  */
4295
4326
  this.backtest = async (symbol, candles, when, backtest) => {
4296
- this.loggerService.log("strategyGlobalService backtest", {
4327
+ this.loggerService.log("strategyCoreService backtest", {
4297
4328
  symbol,
4298
4329
  candleCount: candles.length,
4299
4330
  when,
4300
4331
  backtest,
4301
4332
  });
4333
+ if (!MethodContextService.hasContext()) {
4334
+ throw new Error("strategyCoreService backtest requires a method context");
4335
+ }
4302
4336
  const strategyName = this.methodContextService.context.strategyName;
4303
4337
  await this.validate(symbol, strategyName);
4304
4338
  return await ExecutionContextService.runInContext(async () => {
@@ -4320,7 +4354,7 @@ class StrategyGlobalService {
4320
4354
  * @returns Promise that resolves when stop flag is set
4321
4355
  */
4322
4356
  this.stop = async (ctx, backtest) => {
4323
- this.loggerService.log("strategyGlobalService stop", {
4357
+ this.loggerService.log("strategyCoreService stop", {
4324
4358
  ctx,
4325
4359
  backtest,
4326
4360
  });
@@ -4336,7 +4370,7 @@ class StrategyGlobalService {
4336
4370
  * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
4337
4371
  */
4338
4372
  this.clear = async (ctx) => {
4339
- this.loggerService.log("strategyGlobalService clear", {
4373
+ this.loggerService.log("strategyCoreService clear", {
4340
4374
  ctx,
4341
4375
  });
4342
4376
  if (ctx) {
@@ -4347,14 +4381,14 @@ class StrategyGlobalService {
4347
4381
  }
4348
4382
  }
4349
4383
 
4350
- const METHOD_NAME_GET_TIMEFRAME = "frameGlobalService getTimeframe";
4384
+ const METHOD_NAME_GET_TIMEFRAME = "frameCoreService getTimeframe";
4351
4385
  /**
4352
4386
  * Global service for frame operations.
4353
4387
  *
4354
4388
  * Wraps FrameConnectionService for timeframe generation.
4355
4389
  * Used internally by BacktestLogicPrivateService.
4356
4390
  */
4357
- class FrameGlobalService {
4391
+ class FrameCoreService {
4358
4392
  constructor() {
4359
4393
  this.loggerService = inject(TYPES.loggerService);
4360
4394
  this.frameConnectionService = inject(TYPES.frameConnectionService);
@@ -4370,6 +4404,9 @@ class FrameGlobalService {
4370
4404
  frameName,
4371
4405
  symbol,
4372
4406
  });
4407
+ if (!MethodContextService.hasContext()) {
4408
+ throw new Error("frameCoreService getTimeframe requires a method context");
4409
+ }
4373
4410
  this.frameValidationService.validate(frameName, METHOD_NAME_GET_TIMEFRAME);
4374
4411
  return await this.frameConnectionService.getTimeframe(symbol, frameName);
4375
4412
  };
@@ -4975,9 +5012,9 @@ class WalkerSchemaService {
4975
5012
  class BacktestLogicPrivateService {
4976
5013
  constructor() {
4977
5014
  this.loggerService = inject(TYPES.loggerService);
4978
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
4979
- this.exchangeGlobalService = inject(TYPES.exchangeGlobalService);
4980
- this.frameGlobalService = inject(TYPES.frameGlobalService);
5015
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5016
+ this.exchangeCoreService = inject(TYPES.exchangeCoreService);
5017
+ this.frameCoreService = inject(TYPES.frameCoreService);
4981
5018
  this.methodContextService = inject(TYPES.methodContextService);
4982
5019
  }
4983
5020
  /**
@@ -4999,7 +5036,7 @@ class BacktestLogicPrivateService {
4999
5036
  symbol,
5000
5037
  });
5001
5038
  const backtestStartTime = performance.now();
5002
- const timeframes = await this.frameGlobalService.getTimeframe(symbol, this.methodContextService.context.frameName);
5039
+ const timeframes = await this.frameCoreService.getTimeframe(symbol, this.methodContextService.context.frameName);
5003
5040
  const totalFrames = timeframes.length;
5004
5041
  let i = 0;
5005
5042
  let previousEventTimestamp = null;
@@ -5018,7 +5055,7 @@ class BacktestLogicPrivateService {
5018
5055
  });
5019
5056
  }
5020
5057
  // Check if strategy should stop before processing next frame
5021
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5058
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5022
5059
  this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
5023
5060
  symbol,
5024
5061
  when: when.toISOString(),
@@ -5029,7 +5066,7 @@ class BacktestLogicPrivateService {
5029
5066
  }
5030
5067
  let result;
5031
5068
  try {
5032
- result = await this.strategyGlobalService.tick(symbol, when, true);
5069
+ result = await this.strategyCoreService.tick(symbol, when, true);
5033
5070
  }
5034
5071
  catch (error) {
5035
5072
  console.warn(`backtestLogicPrivateService tick failed, skipping timeframe when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5043,7 +5080,7 @@ class BacktestLogicPrivateService {
5043
5080
  continue;
5044
5081
  }
5045
5082
  // 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))) {
5083
+ if (await and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5047
5084
  this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
5048
5085
  symbol,
5049
5086
  when: when.toISOString(),
@@ -5073,7 +5110,7 @@ class BacktestLogicPrivateService {
5073
5110
  const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
5074
5111
  let candles;
5075
5112
  try {
5076
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5113
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
5077
5114
  }
5078
5115
  catch (error) {
5079
5116
  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 +5139,7 @@ class BacktestLogicPrivateService {
5102
5139
  // и если активируется - продолжит с TP/SL мониторингом
5103
5140
  let backtestResult;
5104
5141
  try {
5105
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5142
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5106
5143
  }
5107
5144
  catch (error) {
5108
5145
  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 +5182,7 @@ class BacktestLogicPrivateService {
5145
5182
  }
5146
5183
  yield backtestResult;
5147
5184
  // Check if strategy should stop after signal is closed
5148
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5185
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5149
5186
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
5150
5187
  symbol,
5151
5188
  signalId: backtestResult.signal.id,
@@ -5172,7 +5209,7 @@ class BacktestLogicPrivateService {
5172
5209
  const totalCandles = signal.minuteEstimatedTime + bufferMinutes;
5173
5210
  let candles;
5174
5211
  try {
5175
- candles = await this.exchangeGlobalService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5212
+ candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
5176
5213
  }
5177
5214
  catch (error) {
5178
5215
  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 +5236,7 @@ class BacktestLogicPrivateService {
5199
5236
  // Вызываем backtest - всегда возвращает closed
5200
5237
  let backtestResult;
5201
5238
  try {
5202
- backtestResult = await this.strategyGlobalService.backtest(symbol, candles, when, true);
5239
+ backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
5203
5240
  }
5204
5241
  catch (error) {
5205
5242
  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 +5275,7 @@ class BacktestLogicPrivateService {
5238
5275
  }
5239
5276
  yield backtestResult;
5240
5277
  // Check if strategy should stop after signal is closed
5241
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5278
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5242
5279
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
5243
5280
  symbol,
5244
5281
  signalId: backtestResult.signal.id,
@@ -5312,7 +5349,7 @@ const TICK_TTL = 1 * 60 * 1000 + 1;
5312
5349
  class LiveLogicPrivateService {
5313
5350
  constructor() {
5314
5351
  this.loggerService = inject(TYPES.loggerService);
5315
- this.strategyGlobalService = inject(TYPES.strategyGlobalService);
5352
+ this.strategyCoreService = inject(TYPES.strategyCoreService);
5316
5353
  this.methodContextService = inject(TYPES.methodContextService);
5317
5354
  }
5318
5355
  /**
@@ -5347,7 +5384,7 @@ class LiveLogicPrivateService {
5347
5384
  const when = new Date();
5348
5385
  let result;
5349
5386
  try {
5350
- result = await this.strategyGlobalService.tick(symbol, when, false);
5387
+ result = await this.strategyCoreService.tick(symbol, when, false);
5351
5388
  }
5352
5389
  catch (error) {
5353
5390
  console.warn(`backtestLogicPrivateService tick failed when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
@@ -5381,7 +5418,7 @@ class LiveLogicPrivateService {
5381
5418
  previousEventTimestamp = currentTimestamp;
5382
5419
  // Check if strategy should stop when idle (no active signal)
5383
5420
  if (result.action === "idle") {
5384
- if (await and(Promise.resolve(true), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5421
+ if (await and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
5385
5422
  this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
5386
5423
  symbol,
5387
5424
  when: when.toISOString(),
@@ -5403,7 +5440,7 @@ class LiveLogicPrivateService {
5403
5440
  yield result;
5404
5441
  // Check if strategy should stop after signal is closed
5405
5442
  if (result.action === "closed") {
5406
- if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5443
+ if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
5407
5444
  this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
5408
5445
  symbol,
5409
5446
  signalId: result.signal.id,
@@ -11839,9 +11876,11 @@ class ConfigValidationService {
11839
11876
  provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
11840
11877
  }
11841
11878
  {
11842
- provide(TYPES.exchangeGlobalService, () => new ExchangeGlobalService());
11843
- provide(TYPES.strategyGlobalService, () => new StrategyGlobalService());
11844
- provide(TYPES.frameGlobalService, () => new FrameGlobalService());
11879
+ provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
11880
+ provide(TYPES.strategyCoreService, () => new StrategyCoreService());
11881
+ provide(TYPES.frameCoreService, () => new FrameCoreService());
11882
+ }
11883
+ {
11845
11884
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
11846
11885
  provide(TYPES.riskGlobalService, () => new RiskGlobalService());
11847
11886
  provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
@@ -11911,10 +11950,12 @@ const schemaServices = {
11911
11950
  riskSchemaService: inject(TYPES.riskSchemaService),
11912
11951
  optimizerSchemaService: inject(TYPES.optimizerSchemaService),
11913
11952
  };
11953
+ const coreServices = {
11954
+ exchangeCoreService: inject(TYPES.exchangeCoreService),
11955
+ strategyCoreService: inject(TYPES.strategyCoreService),
11956
+ frameCoreService: inject(TYPES.frameCoreService),
11957
+ };
11914
11958
  const globalServices = {
11915
- exchangeGlobalService: inject(TYPES.exchangeGlobalService),
11916
- strategyGlobalService: inject(TYPES.strategyGlobalService),
11917
- frameGlobalService: inject(TYPES.frameGlobalService),
11918
11959
  sizingGlobalService: inject(TYPES.sizingGlobalService),
11919
11960
  riskGlobalService: inject(TYPES.riskGlobalService),
11920
11961
  optimizerGlobalService: inject(TYPES.optimizerGlobalService),
@@ -11963,6 +12004,7 @@ const backtest = {
11963
12004
  ...contextServices,
11964
12005
  ...connectionServices,
11965
12006
  ...schemaServices,
12007
+ ...coreServices,
11966
12008
  ...globalServices,
11967
12009
  ...commandServices,
11968
12010
  ...logicPrivateServices,
@@ -11997,6 +12039,7 @@ function setLogger(logger) {
11997
12039
  /**
11998
12040
  * Sets global configuration parameters for the framework.
11999
12041
  * @param config - Partial configuration object to override default settings
12042
+ * @param _unsafe - Skip config validations - required for testbed
12000
12043
  *
12001
12044
  * @example
12002
12045
  * ```typescript
@@ -13859,7 +13902,7 @@ class BacktestInstance {
13859
13902
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
13860
13903
  }
13861
13904
  {
13862
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
13905
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
13863
13906
  }
13864
13907
  {
13865
13908
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -13894,8 +13937,8 @@ class BacktestInstance {
13894
13937
  });
13895
13938
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
13896
13939
  return () => {
13897
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
13898
- backtest$1.strategyGlobalService
13940
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, true);
13941
+ backtest$1.strategyCoreService
13899
13942
  .getPendingSignal(symbol, context.strategyName)
13900
13943
  .then(async (pendingSignal) => {
13901
13944
  if (pendingSignal) {
@@ -13936,7 +13979,7 @@ class BacktestInstance {
13936
13979
  symbol,
13937
13980
  strategyName,
13938
13981
  });
13939
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
13982
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
13940
13983
  };
13941
13984
  /**
13942
13985
  * Gets statistical data from all closed signals for a symbol-strategy pair.
@@ -14351,7 +14394,7 @@ class LiveInstance {
14351
14394
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
14352
14395
  }
14353
14396
  {
14354
- backtest$1.strategyGlobalService.clear({ symbol, strategyName: context.strategyName });
14397
+ backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
14355
14398
  }
14356
14399
  {
14357
14400
  const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
@@ -14386,8 +14429,8 @@ class LiveInstance {
14386
14429
  });
14387
14430
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
14388
14431
  return () => {
14389
- backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
14390
- backtest$1.strategyGlobalService
14432
+ backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, false);
14433
+ backtest$1.strategyCoreService
14391
14434
  .getPendingSignal(symbol, context.strategyName)
14392
14435
  .then(async (pendingSignal) => {
14393
14436
  if (pendingSignal) {
@@ -14428,7 +14471,7 @@ class LiveInstance {
14428
14471
  symbol,
14429
14472
  strategyName,
14430
14473
  });
14431
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
14474
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, false);
14432
14475
  };
14433
14476
  /**
14434
14477
  * Gets statistical data from all live trading events for a symbol-strategy pair.
@@ -15109,7 +15152,7 @@ class WalkerInstance {
15109
15152
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
15110
15153
  }
15111
15154
  {
15112
- backtest$1.strategyGlobalService.clear({ symbol, strategyName });
15155
+ backtest$1.strategyCoreService.clear({ symbol, strategyName });
15113
15156
  }
15114
15157
  {
15115
15158
  const { riskName } = backtest$1.strategySchemaService.get(strategyName);
@@ -15149,7 +15192,7 @@ class WalkerInstance {
15149
15192
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
15150
15193
  return () => {
15151
15194
  for (const strategyName of walkerSchema.strategies) {
15152
- backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15195
+ backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15153
15196
  walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
15154
15197
  }
15155
15198
  if (!this._isDone) {
@@ -15195,7 +15238,7 @@ class WalkerInstance {
15195
15238
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
15196
15239
  for (const strategyName of walkerSchema.strategies) {
15197
15240
  await walkerStopSubject.next({ symbol, strategyName, walkerName });
15198
- await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
15241
+ await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15199
15242
  }
15200
15243
  };
15201
15244
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.5.13",
3
+ "version": "1.5.14",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
package/types.d.ts CHANGED
@@ -159,6 +159,7 @@ declare function setLogger(logger: ILogger): void;
159
159
  /**
160
160
  * Sets global configuration parameters for the framework.
161
161
  * @param config - Partial configuration object to override default settings
162
+ * @param _unsafe - Skip config validations - required for testbed
162
163
  *
163
164
  * @example
164
165
  * ```typescript
@@ -540,6 +541,8 @@ declare const MethodContextService: (new () => {
540
541
  interface IRiskCheckArgs {
541
542
  /** Trading pair symbol (e.g., "BTCUSDT") */
542
543
  symbol: string;
544
+ /** Pending signal to apply */
545
+ pendingSignal: ISignalDto;
543
546
  /** Strategy name requesting to open a position */
544
547
  strategyName: StrategyName;
545
548
  /** Exchange name */
@@ -576,6 +579,8 @@ interface IRiskCallbacks {
576
579
  * Extends IRiskCheckArgs with portfolio state data.
577
580
  */
578
581
  interface IRiskValidationPayload extends IRiskCheckArgs {
582
+ /** Pending signal to apply */
583
+ pendingSignal: ISignalDto;
579
584
  /** Number of currently active positions across all strategies */
580
585
  activePositionCount: number;
581
586
  /** List of currently active positions across all strategies */
@@ -7228,7 +7233,7 @@ declare class RiskConnectionService {
7228
7233
  *
7229
7234
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
7230
7235
  */
7231
- declare class ExchangeGlobalService {
7236
+ declare class ExchangeCoreService {
7232
7237
  private readonly loggerService;
7233
7238
  private readonly exchangeConnectionService;
7234
7239
  private readonly methodContextService;
@@ -7302,7 +7307,7 @@ declare class ExchangeGlobalService {
7302
7307
  *
7303
7308
  * Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
7304
7309
  */
7305
- declare class StrategyGlobalService {
7310
+ declare class StrategyCoreService {
7306
7311
  private readonly loggerService;
7307
7312
  private readonly strategyConnectionService;
7308
7313
  private readonly strategySchemaService;
@@ -7399,7 +7404,7 @@ declare class StrategyGlobalService {
7399
7404
  * Wraps FrameConnectionService for timeframe generation.
7400
7405
  * Used internally by BacktestLogicPrivateService.
7401
7406
  */
7402
- declare class FrameGlobalService {
7407
+ declare class FrameCoreService {
7403
7408
  private readonly loggerService;
7404
7409
  private readonly frameConnectionService;
7405
7410
  private readonly frameValidationService;
@@ -7823,9 +7828,9 @@ declare class WalkerSchemaService {
7823
7828
  */
7824
7829
  declare class BacktestLogicPrivateService {
7825
7830
  private readonly loggerService;
7826
- private readonly strategyGlobalService;
7827
- private readonly exchangeGlobalService;
7828
- private readonly frameGlobalService;
7831
+ private readonly strategyCoreService;
7832
+ private readonly exchangeCoreService;
7833
+ private readonly frameCoreService;
7829
7834
  private readonly methodContextService;
7830
7835
  /**
7831
7836
  * Runs backtest for a symbol, streaming closed signals as async generator.
@@ -7862,7 +7867,7 @@ declare class BacktestLogicPrivateService {
7862
7867
  */
7863
7868
  declare class LiveLogicPrivateService {
7864
7869
  private readonly loggerService;
7865
- private readonly strategyGlobalService;
7870
+ private readonly strategyCoreService;
7866
7871
  private readonly methodContextService;
7867
7872
  /**
7868
7873
  * Runs live trading for a symbol, streaming results as async generator.
@@ -9293,13 +9298,13 @@ declare const backtest: {
9293
9298
  liveCommandService: LiveCommandService;
9294
9299
  backtestCommandService: BacktestCommandService;
9295
9300
  walkerCommandService: WalkerCommandService;
9296
- exchangeGlobalService: ExchangeGlobalService;
9297
- strategyGlobalService: StrategyGlobalService;
9298
- frameGlobalService: FrameGlobalService;
9299
9301
  sizingGlobalService: SizingGlobalService;
9300
9302
  riskGlobalService: RiskGlobalService;
9301
9303
  optimizerGlobalService: OptimizerGlobalService;
9302
9304
  partialGlobalService: PartialGlobalService;
9305
+ exchangeCoreService: ExchangeCoreService;
9306
+ strategyCoreService: StrategyCoreService;
9307
+ frameCoreService: FrameCoreService;
9303
9308
  exchangeSchemaService: ExchangeSchemaService;
9304
9309
  strategySchemaService: StrategySchemaService;
9305
9310
  frameSchemaService: FrameSchemaService;