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