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 +137 -105
- package/build/index.mjs +137 -105
- package/package.json +1 -1
- package/types.d.ts +31 -10
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 = "
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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 = "
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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 = "
|
|
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
|
|
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.
|
|
4981
|
-
this.
|
|
4982
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
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.
|
|
11845
|
-
provide(TYPES.
|
|
11846
|
-
provide(TYPES.
|
|
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.
|
|
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.
|
|
13900
|
-
backtest$1.
|
|
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.
|
|
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.
|
|
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.
|
|
14392
|
-
backtest$1.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
15232
|
+
await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
|
|
15201
15233
|
}
|
|
15202
15234
|
};
|
|
15203
15235
|
/**
|