backtest-kit 1.5.12 → 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 +444 -97
- package/build/index.mjs +443 -98
- package/package.json +1 -1
- package/types.d.ts +280 -27
package/build/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, ToolRegistry, isObject, and, resolveDocuments, str, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
|
|
1
2
|
import { createActivator } from 'di-kit';
|
|
2
3
|
import { scoped } from 'di-scoped';
|
|
3
|
-
import { errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, ToolRegistry, isObject, and, resolveDocuments, str, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
|
|
4
4
|
import fs, { mkdir, writeFile } from 'fs/promises';
|
|
5
5
|
import path, { join } from 'path';
|
|
6
6
|
import crypto from 'crypto';
|
|
@@ -18,12 +18,32 @@ const GLOBAL_CONFIG = {
|
|
|
18
18
|
* Default: 5 candles (last 5 minutes when using 1m interval)
|
|
19
19
|
*/
|
|
20
20
|
CC_AVG_PRICE_CANDLES_COUNT: 5,
|
|
21
|
+
/**
|
|
22
|
+
* Slippage percentage applied to entry and exit prices.
|
|
23
|
+
* Simulates market impact and order book depth.
|
|
24
|
+
* Applied twice (entry and exit) for realistic execution simulation.
|
|
25
|
+
* Default: 0.1% per transaction
|
|
26
|
+
*/
|
|
27
|
+
CC_PERCENT_SLIPPAGE: 0.1,
|
|
28
|
+
/**
|
|
29
|
+
* Fee percentage charged per transaction.
|
|
30
|
+
* Applied twice (entry and exit) for total fee calculation.
|
|
31
|
+
* Default: 0.1% per transaction (total 0.2%)
|
|
32
|
+
*/
|
|
33
|
+
CC_PERCENT_FEE: 0.1,
|
|
21
34
|
/**
|
|
22
35
|
* Minimum TakeProfit distance from priceOpen (percentage)
|
|
23
|
-
* Must be greater than
|
|
24
|
-
*
|
|
36
|
+
* Must be greater than (slippage + fees) to ensure profitable trades
|
|
37
|
+
*
|
|
38
|
+
* Calculation:
|
|
39
|
+
* - Slippage effect: ~0.2% (0.1% × 2 transactions)
|
|
40
|
+
* - Fees: 0.2% (0.1% × 2 transactions)
|
|
41
|
+
* - Minimum profit buffer: 0.1%
|
|
42
|
+
* - Total: 0.5%
|
|
43
|
+
*
|
|
44
|
+
* Default: 0.5% (covers all costs + minimum profit margin)
|
|
25
45
|
*/
|
|
26
|
-
CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.
|
|
46
|
+
CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.5,
|
|
27
47
|
/**
|
|
28
48
|
* Minimum StopLoss distance from priceOpen (percentage)
|
|
29
49
|
* Prevents signals from being immediately stopped out due to price volatility
|
|
@@ -90,6 +110,7 @@ const GLOBAL_CONFIG = {
|
|
|
90
110
|
*/
|
|
91
111
|
CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: 5,
|
|
92
112
|
};
|
|
113
|
+
const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
|
|
93
114
|
|
|
94
115
|
const { init, inject, provide } = createActivator("backtest");
|
|
95
116
|
|
|
@@ -144,10 +165,12 @@ const schemaServices$1 = {
|
|
|
144
165
|
riskSchemaService: Symbol('riskSchemaService'),
|
|
145
166
|
optimizerSchemaService: Symbol('optimizerSchemaService'),
|
|
146
167
|
};
|
|
168
|
+
const coreServices$1 = {
|
|
169
|
+
exchangeCoreService: Symbol('exchangeCoreService'),
|
|
170
|
+
strategyCoreService: Symbol('strategyCoreService'),
|
|
171
|
+
frameCoreService: Symbol('frameCoreService'),
|
|
172
|
+
};
|
|
147
173
|
const globalServices$1 = {
|
|
148
|
-
exchangeGlobalService: Symbol('exchangeGlobalService'),
|
|
149
|
-
strategyGlobalService: Symbol('strategyGlobalService'),
|
|
150
|
-
frameGlobalService: Symbol('frameGlobalService'),
|
|
151
174
|
sizingGlobalService: Symbol('sizingGlobalService'),
|
|
152
175
|
riskGlobalService: Symbol('riskGlobalService'),
|
|
153
176
|
optimizerGlobalService: Symbol('optimizerGlobalService'),
|
|
@@ -186,6 +209,7 @@ const validationServices$1 = {
|
|
|
186
209
|
sizingValidationService: Symbol('sizingValidationService'),
|
|
187
210
|
riskValidationService: Symbol('riskValidationService'),
|
|
188
211
|
optimizerValidationService: Symbol('optimizerValidationService'),
|
|
212
|
+
configValidationService: Symbol('configValidationService'),
|
|
189
213
|
};
|
|
190
214
|
const templateServices$1 = {
|
|
191
215
|
optimizerTemplateService: Symbol('optimizerTemplateService'),
|
|
@@ -195,6 +219,7 @@ const TYPES = {
|
|
|
195
219
|
...contextServices$1,
|
|
196
220
|
...connectionServices$1,
|
|
197
221
|
...schemaServices$1,
|
|
222
|
+
...coreServices$1,
|
|
198
223
|
...globalServices$1,
|
|
199
224
|
...commandServices$1,
|
|
200
225
|
...logicPrivateServices$1,
|
|
@@ -715,16 +740,6 @@ class ExchangeConnectionService {
|
|
|
715
740
|
}
|
|
716
741
|
}
|
|
717
742
|
|
|
718
|
-
/**
|
|
719
|
-
* Slippage percentage applied to entry and exit prices.
|
|
720
|
-
* Simulates market impact and order book depth.
|
|
721
|
-
*/
|
|
722
|
-
const PERCENT_SLIPPAGE = 0.1;
|
|
723
|
-
/**
|
|
724
|
-
* Fee percentage charged per transaction.
|
|
725
|
-
* Applied twice (entry and exit) for total fee calculation.
|
|
726
|
-
*/
|
|
727
|
-
const PERCENT_FEE = 0.1;
|
|
728
743
|
/**
|
|
729
744
|
* Calculates profit/loss for a closed signal with slippage and fees.
|
|
730
745
|
*
|
|
@@ -760,16 +775,16 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
760
775
|
let priceCloseWithSlippage;
|
|
761
776
|
if (signal.position === "long") {
|
|
762
777
|
// LONG: покупаем дороже, продаем дешевле
|
|
763
|
-
priceOpenWithSlippage = priceOpen * (1 +
|
|
764
|
-
priceCloseWithSlippage = priceClose * (1 -
|
|
778
|
+
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
779
|
+
priceCloseWithSlippage = priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
765
780
|
}
|
|
766
781
|
else {
|
|
767
782
|
// SHORT: продаем дешевле, покупаем дороже
|
|
768
|
-
priceOpenWithSlippage = priceOpen * (1 -
|
|
769
|
-
priceCloseWithSlippage = priceClose * (1 +
|
|
783
|
+
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
784
|
+
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
770
785
|
}
|
|
771
786
|
// Применяем комиссию дважды (при открытии и закрытии)
|
|
772
|
-
const totalFee =
|
|
787
|
+
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
|
|
773
788
|
let pnlPercentage;
|
|
774
789
|
if (signal.position === "long") {
|
|
775
790
|
// LONG: прибыль при росте цены
|
|
@@ -1987,15 +2002,6 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
1987
2002
|
self._lastSignalTimestamp = currentTime;
|
|
1988
2003
|
}
|
|
1989
2004
|
const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
|
|
1990
|
-
if (await not(self.params.risk.checkSignal({
|
|
1991
|
-
symbol: self.params.execution.context.symbol,
|
|
1992
|
-
strategyName: self.params.method.context.strategyName,
|
|
1993
|
-
exchangeName: self.params.method.context.exchangeName,
|
|
1994
|
-
currentPrice,
|
|
1995
|
-
timestamp: currentTime,
|
|
1996
|
-
}))) {
|
|
1997
|
-
return null;
|
|
1998
|
-
}
|
|
1999
2005
|
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
2000
2006
|
const signal = await Promise.race([
|
|
2001
2007
|
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
|
|
@@ -2010,6 +2016,16 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
|
|
|
2010
2016
|
if (self._isStopped) {
|
|
2011
2017
|
return null;
|
|
2012
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
|
+
}
|
|
2013
2029
|
// Если priceOpen указан - проверяем нужно ли ждать активации или открыть сразу
|
|
2014
2030
|
if (signal.priceOpen !== undefined) {
|
|
2015
2031
|
// КРИТИЧЕСКАЯ ПРОВЕРКА: достигнут ли priceOpen?
|
|
@@ -2241,6 +2257,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
2241
2257
|
});
|
|
2242
2258
|
if (await not(self.params.risk.checkSignal({
|
|
2243
2259
|
symbol: self.params.execution.context.symbol,
|
|
2260
|
+
pendingSignal: scheduled,
|
|
2244
2261
|
strategyName: self.params.method.context.strategyName,
|
|
2245
2262
|
exchangeName: self.params.method.context.exchangeName,
|
|
2246
2263
|
currentPrice: scheduled.priceOpen,
|
|
@@ -2324,6 +2341,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
|
|
|
2324
2341
|
};
|
|
2325
2342
|
const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
|
|
2326
2343
|
if (await not(self.params.risk.checkSignal({
|
|
2344
|
+
pendingSignal: signal,
|
|
2327
2345
|
symbol: self.params.execution.context.symbol,
|
|
2328
2346
|
strategyName: self.params.method.context.strategyName,
|
|
2329
2347
|
exchangeName: self.params.method.context.exchangeName,
|
|
@@ -2550,6 +2568,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
2550
2568
|
pendingAt: activationTime,
|
|
2551
2569
|
});
|
|
2552
2570
|
if (await not(self.params.risk.checkSignal({
|
|
2571
|
+
pendingSignal: scheduled,
|
|
2553
2572
|
symbol: self.params.execution.context.symbol,
|
|
2554
2573
|
strategyName: self.params.method.context.strategyName,
|
|
2555
2574
|
exchangeName: self.params.method.context.exchangeName,
|
|
@@ -4011,7 +4030,7 @@ class RiskConnectionService {
|
|
|
4011
4030
|
}
|
|
4012
4031
|
}
|
|
4013
4032
|
|
|
4014
|
-
const METHOD_NAME_VALIDATE$1 = "
|
|
4033
|
+
const METHOD_NAME_VALIDATE$1 = "exchangeCoreService validate";
|
|
4015
4034
|
/**
|
|
4016
4035
|
* Global service for exchange operations with execution context injection.
|
|
4017
4036
|
*
|
|
@@ -4020,7 +4039,7 @@ const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
|
|
|
4020
4039
|
*
|
|
4021
4040
|
* Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
|
|
4022
4041
|
*/
|
|
4023
|
-
class
|
|
4042
|
+
class ExchangeCoreService {
|
|
4024
4043
|
constructor() {
|
|
4025
4044
|
this.loggerService = inject(TYPES.loggerService);
|
|
4026
4045
|
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
@@ -4050,13 +4069,16 @@ class ExchangeGlobalService {
|
|
|
4050
4069
|
* @returns Promise resolving to array of candles
|
|
4051
4070
|
*/
|
|
4052
4071
|
this.getCandles = async (symbol, interval, limit, when, backtest) => {
|
|
4053
|
-
this.loggerService.log("
|
|
4072
|
+
this.loggerService.log("exchangeCoreService getCandles", {
|
|
4054
4073
|
symbol,
|
|
4055
4074
|
interval,
|
|
4056
4075
|
limit,
|
|
4057
4076
|
when,
|
|
4058
4077
|
backtest,
|
|
4059
4078
|
});
|
|
4079
|
+
if (!MethodContextService.hasContext()) {
|
|
4080
|
+
throw new Error("exchangeCoreService getCandles requires a method context");
|
|
4081
|
+
}
|
|
4060
4082
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4061
4083
|
return await ExecutionContextService.runInContext(async () => {
|
|
4062
4084
|
return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
|
|
@@ -4077,13 +4099,16 @@ class ExchangeGlobalService {
|
|
|
4077
4099
|
* @returns Promise resolving to array of future candles
|
|
4078
4100
|
*/
|
|
4079
4101
|
this.getNextCandles = async (symbol, interval, limit, when, backtest) => {
|
|
4080
|
-
this.loggerService.log("
|
|
4102
|
+
this.loggerService.log("exchangeCoreService getNextCandles", {
|
|
4081
4103
|
symbol,
|
|
4082
4104
|
interval,
|
|
4083
4105
|
limit,
|
|
4084
4106
|
when,
|
|
4085
4107
|
backtest,
|
|
4086
4108
|
});
|
|
4109
|
+
if (!MethodContextService.hasContext()) {
|
|
4110
|
+
throw new Error("exchangeCoreService getNextCandles requires a method context");
|
|
4111
|
+
}
|
|
4087
4112
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4088
4113
|
return await ExecutionContextService.runInContext(async () => {
|
|
4089
4114
|
return await this.exchangeConnectionService.getNextCandles(symbol, interval, limit);
|
|
@@ -4102,11 +4127,14 @@ class ExchangeGlobalService {
|
|
|
4102
4127
|
* @returns Promise resolving to VWAP price
|
|
4103
4128
|
*/
|
|
4104
4129
|
this.getAveragePrice = async (symbol, when, backtest) => {
|
|
4105
|
-
this.loggerService.log("
|
|
4130
|
+
this.loggerService.log("exchangeCoreService getAveragePrice", {
|
|
4106
4131
|
symbol,
|
|
4107
4132
|
when,
|
|
4108
4133
|
backtest,
|
|
4109
4134
|
});
|
|
4135
|
+
if (!MethodContextService.hasContext()) {
|
|
4136
|
+
throw new Error("exchangeCoreService getAveragePrice requires a method context");
|
|
4137
|
+
}
|
|
4110
4138
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4111
4139
|
return await ExecutionContextService.runInContext(async () => {
|
|
4112
4140
|
return await this.exchangeConnectionService.getAveragePrice(symbol);
|
|
@@ -4126,12 +4154,15 @@ class ExchangeGlobalService {
|
|
|
4126
4154
|
* @returns Promise resolving to formatted price string
|
|
4127
4155
|
*/
|
|
4128
4156
|
this.formatPrice = async (symbol, price, when, backtest) => {
|
|
4129
|
-
this.loggerService.log("
|
|
4157
|
+
this.loggerService.log("exchangeCoreService formatPrice", {
|
|
4130
4158
|
symbol,
|
|
4131
4159
|
price,
|
|
4132
4160
|
when,
|
|
4133
4161
|
backtest,
|
|
4134
4162
|
});
|
|
4163
|
+
if (!MethodContextService.hasContext()) {
|
|
4164
|
+
throw new Error("exchangeCoreService formatPrice requires a method context");
|
|
4165
|
+
}
|
|
4135
4166
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4136
4167
|
return await ExecutionContextService.runInContext(async () => {
|
|
4137
4168
|
return await this.exchangeConnectionService.formatPrice(symbol, price);
|
|
@@ -4151,12 +4182,15 @@ class ExchangeGlobalService {
|
|
|
4151
4182
|
* @returns Promise resolving to formatted quantity string
|
|
4152
4183
|
*/
|
|
4153
4184
|
this.formatQuantity = async (symbol, quantity, when, backtest) => {
|
|
4154
|
-
this.loggerService.log("
|
|
4185
|
+
this.loggerService.log("exchangeCoreService formatQuantity", {
|
|
4155
4186
|
symbol,
|
|
4156
4187
|
quantity,
|
|
4157
4188
|
when,
|
|
4158
4189
|
backtest,
|
|
4159
4190
|
});
|
|
4191
|
+
if (!MethodContextService.hasContext()) {
|
|
4192
|
+
throw new Error("exchangeCoreService formatQuantity requires a method context");
|
|
4193
|
+
}
|
|
4160
4194
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4161
4195
|
return await ExecutionContextService.runInContext(async () => {
|
|
4162
4196
|
return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
|
|
@@ -4169,7 +4203,7 @@ class ExchangeGlobalService {
|
|
|
4169
4203
|
}
|
|
4170
4204
|
}
|
|
4171
4205
|
|
|
4172
|
-
const METHOD_NAME_VALIDATE = "
|
|
4206
|
+
const METHOD_NAME_VALIDATE = "strategyCoreService validate";
|
|
4173
4207
|
/**
|
|
4174
4208
|
* Global service for strategy operations with execution context injection.
|
|
4175
4209
|
*
|
|
@@ -4178,7 +4212,7 @@ const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
|
|
|
4178
4212
|
*
|
|
4179
4213
|
* Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
|
|
4180
4214
|
*/
|
|
4181
|
-
class
|
|
4215
|
+
class StrategyCoreService {
|
|
4182
4216
|
constructor() {
|
|
4183
4217
|
this.loggerService = inject(TYPES.loggerService);
|
|
4184
4218
|
this.strategyConnectionService = inject(TYPES.strategyConnectionService);
|
|
@@ -4216,10 +4250,13 @@ class StrategyGlobalService {
|
|
|
4216
4250
|
* @returns Promise resolving to pending signal or null
|
|
4217
4251
|
*/
|
|
4218
4252
|
this.getPendingSignal = async (symbol, strategyName) => {
|
|
4219
|
-
this.loggerService.log("
|
|
4253
|
+
this.loggerService.log("strategyCoreService getPendingSignal", {
|
|
4220
4254
|
symbol,
|
|
4221
4255
|
strategyName,
|
|
4222
4256
|
});
|
|
4257
|
+
if (!MethodContextService.hasContext()) {
|
|
4258
|
+
throw new Error("strategyCoreService getPendingSignal requires a method context");
|
|
4259
|
+
}
|
|
4223
4260
|
await this.validate(symbol, strategyName);
|
|
4224
4261
|
return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
|
|
4225
4262
|
};
|
|
@@ -4234,10 +4271,13 @@ class StrategyGlobalService {
|
|
|
4234
4271
|
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
4235
4272
|
*/
|
|
4236
4273
|
this.getStopped = async (symbol, strategyName) => {
|
|
4237
|
-
this.loggerService.log("
|
|
4274
|
+
this.loggerService.log("strategyCoreService getStopped", {
|
|
4238
4275
|
symbol,
|
|
4239
4276
|
strategyName,
|
|
4240
4277
|
});
|
|
4278
|
+
if (!MethodContextService.hasContext()) {
|
|
4279
|
+
throw new Error("strategyCoreService getStopped requires a method context");
|
|
4280
|
+
}
|
|
4241
4281
|
await this.validate(symbol, strategyName);
|
|
4242
4282
|
return await this.strategyConnectionService.getStopped(symbol, strategyName);
|
|
4243
4283
|
};
|
|
@@ -4253,11 +4293,14 @@ class StrategyGlobalService {
|
|
|
4253
4293
|
* @returns Discriminated union of tick result (idle, opened, active, closed)
|
|
4254
4294
|
*/
|
|
4255
4295
|
this.tick = async (symbol, when, backtest) => {
|
|
4256
|
-
this.loggerService.log("
|
|
4296
|
+
this.loggerService.log("strategyCoreService tick", {
|
|
4257
4297
|
symbol,
|
|
4258
4298
|
when,
|
|
4259
4299
|
backtest,
|
|
4260
4300
|
});
|
|
4301
|
+
if (!MethodContextService.hasContext()) {
|
|
4302
|
+
throw new Error("strategyCoreService tick requires a method context");
|
|
4303
|
+
}
|
|
4261
4304
|
const strategyName = this.methodContextService.context.strategyName;
|
|
4262
4305
|
await this.validate(symbol, strategyName);
|
|
4263
4306
|
return await ExecutionContextService.runInContext(async () => {
|
|
@@ -4281,12 +4324,15 @@ class StrategyGlobalService {
|
|
|
4281
4324
|
* @returns Closed signal result with PNL
|
|
4282
4325
|
*/
|
|
4283
4326
|
this.backtest = async (symbol, candles, when, backtest) => {
|
|
4284
|
-
this.loggerService.log("
|
|
4327
|
+
this.loggerService.log("strategyCoreService backtest", {
|
|
4285
4328
|
symbol,
|
|
4286
4329
|
candleCount: candles.length,
|
|
4287
4330
|
when,
|
|
4288
4331
|
backtest,
|
|
4289
4332
|
});
|
|
4333
|
+
if (!MethodContextService.hasContext()) {
|
|
4334
|
+
throw new Error("strategyCoreService backtest requires a method context");
|
|
4335
|
+
}
|
|
4290
4336
|
const strategyName = this.methodContextService.context.strategyName;
|
|
4291
4337
|
await this.validate(symbol, strategyName);
|
|
4292
4338
|
return await ExecutionContextService.runInContext(async () => {
|
|
@@ -4308,7 +4354,7 @@ class StrategyGlobalService {
|
|
|
4308
4354
|
* @returns Promise that resolves when stop flag is set
|
|
4309
4355
|
*/
|
|
4310
4356
|
this.stop = async (ctx, backtest) => {
|
|
4311
|
-
this.loggerService.log("
|
|
4357
|
+
this.loggerService.log("strategyCoreService stop", {
|
|
4312
4358
|
ctx,
|
|
4313
4359
|
backtest,
|
|
4314
4360
|
});
|
|
@@ -4324,7 +4370,7 @@ class StrategyGlobalService {
|
|
|
4324
4370
|
* @param ctx - Optional context with symbol and strategyName (clears all if not provided)
|
|
4325
4371
|
*/
|
|
4326
4372
|
this.clear = async (ctx) => {
|
|
4327
|
-
this.loggerService.log("
|
|
4373
|
+
this.loggerService.log("strategyCoreService clear", {
|
|
4328
4374
|
ctx,
|
|
4329
4375
|
});
|
|
4330
4376
|
if (ctx) {
|
|
@@ -4335,14 +4381,14 @@ class StrategyGlobalService {
|
|
|
4335
4381
|
}
|
|
4336
4382
|
}
|
|
4337
4383
|
|
|
4338
|
-
const METHOD_NAME_GET_TIMEFRAME = "
|
|
4384
|
+
const METHOD_NAME_GET_TIMEFRAME = "frameCoreService getTimeframe";
|
|
4339
4385
|
/**
|
|
4340
4386
|
* Global service for frame operations.
|
|
4341
4387
|
*
|
|
4342
4388
|
* Wraps FrameConnectionService for timeframe generation.
|
|
4343
4389
|
* Used internally by BacktestLogicPrivateService.
|
|
4344
4390
|
*/
|
|
4345
|
-
class
|
|
4391
|
+
class FrameCoreService {
|
|
4346
4392
|
constructor() {
|
|
4347
4393
|
this.loggerService = inject(TYPES.loggerService);
|
|
4348
4394
|
this.frameConnectionService = inject(TYPES.frameConnectionService);
|
|
@@ -4358,6 +4404,9 @@ class FrameGlobalService {
|
|
|
4358
4404
|
frameName,
|
|
4359
4405
|
symbol,
|
|
4360
4406
|
});
|
|
4407
|
+
if (!MethodContextService.hasContext()) {
|
|
4408
|
+
throw new Error("frameCoreService getTimeframe requires a method context");
|
|
4409
|
+
}
|
|
4361
4410
|
this.frameValidationService.validate(frameName, METHOD_NAME_GET_TIMEFRAME);
|
|
4362
4411
|
return await this.frameConnectionService.getTimeframe(symbol, frameName);
|
|
4363
4412
|
};
|
|
@@ -4963,9 +5012,9 @@ class WalkerSchemaService {
|
|
|
4963
5012
|
class BacktestLogicPrivateService {
|
|
4964
5013
|
constructor() {
|
|
4965
5014
|
this.loggerService = inject(TYPES.loggerService);
|
|
4966
|
-
this.
|
|
4967
|
-
this.
|
|
4968
|
-
this.
|
|
5015
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
5016
|
+
this.exchangeCoreService = inject(TYPES.exchangeCoreService);
|
|
5017
|
+
this.frameCoreService = inject(TYPES.frameCoreService);
|
|
4969
5018
|
this.methodContextService = inject(TYPES.methodContextService);
|
|
4970
5019
|
}
|
|
4971
5020
|
/**
|
|
@@ -4987,7 +5036,7 @@ class BacktestLogicPrivateService {
|
|
|
4987
5036
|
symbol,
|
|
4988
5037
|
});
|
|
4989
5038
|
const backtestStartTime = performance.now();
|
|
4990
|
-
const timeframes = await this.
|
|
5039
|
+
const timeframes = await this.frameCoreService.getTimeframe(symbol, this.methodContextService.context.frameName);
|
|
4991
5040
|
const totalFrames = timeframes.length;
|
|
4992
5041
|
let i = 0;
|
|
4993
5042
|
let previousEventTimestamp = null;
|
|
@@ -5006,7 +5055,7 @@ class BacktestLogicPrivateService {
|
|
|
5006
5055
|
});
|
|
5007
5056
|
}
|
|
5008
5057
|
// Check if strategy should stop before processing next frame
|
|
5009
|
-
if (await this.
|
|
5058
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5010
5059
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
|
|
5011
5060
|
symbol,
|
|
5012
5061
|
when: when.toISOString(),
|
|
@@ -5017,7 +5066,7 @@ class BacktestLogicPrivateService {
|
|
|
5017
5066
|
}
|
|
5018
5067
|
let result;
|
|
5019
5068
|
try {
|
|
5020
|
-
result = await this.
|
|
5069
|
+
result = await this.strategyCoreService.tick(symbol, when, true);
|
|
5021
5070
|
}
|
|
5022
5071
|
catch (error) {
|
|
5023
5072
|
console.warn(`backtestLogicPrivateService tick failed, skipping timeframe when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5031,7 +5080,7 @@ class BacktestLogicPrivateService {
|
|
|
5031
5080
|
continue;
|
|
5032
5081
|
}
|
|
5033
5082
|
// Check if strategy should stop when idle (no active signal)
|
|
5034
|
-
if (await and(Promise.resolve(result.action === "idle"), this.
|
|
5083
|
+
if (await and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
5035
5084
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
|
|
5036
5085
|
symbol,
|
|
5037
5086
|
when: when.toISOString(),
|
|
@@ -5061,7 +5110,7 @@ class BacktestLogicPrivateService {
|
|
|
5061
5110
|
const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
|
|
5062
5111
|
let candles;
|
|
5063
5112
|
try {
|
|
5064
|
-
candles = await this.
|
|
5113
|
+
candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
|
|
5065
5114
|
}
|
|
5066
5115
|
catch (error) {
|
|
5067
5116
|
console.warn(`backtestLogicPrivateService getNextCandles failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5090,7 +5139,7 @@ class BacktestLogicPrivateService {
|
|
|
5090
5139
|
// и если активируется - продолжит с TP/SL мониторингом
|
|
5091
5140
|
let backtestResult;
|
|
5092
5141
|
try {
|
|
5093
|
-
backtestResult = await this.
|
|
5142
|
+
backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
|
|
5094
5143
|
}
|
|
5095
5144
|
catch (error) {
|
|
5096
5145
|
console.warn(`backtestLogicPrivateService backtest failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5133,7 +5182,7 @@ class BacktestLogicPrivateService {
|
|
|
5133
5182
|
}
|
|
5134
5183
|
yield backtestResult;
|
|
5135
5184
|
// Check if strategy should stop after signal is closed
|
|
5136
|
-
if (await this.
|
|
5185
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5137
5186
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
|
|
5138
5187
|
symbol,
|
|
5139
5188
|
signalId: backtestResult.signal.id,
|
|
@@ -5160,7 +5209,7 @@ class BacktestLogicPrivateService {
|
|
|
5160
5209
|
const totalCandles = signal.minuteEstimatedTime + bufferMinutes;
|
|
5161
5210
|
let candles;
|
|
5162
5211
|
try {
|
|
5163
|
-
candles = await this.
|
|
5212
|
+
candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
|
|
5164
5213
|
}
|
|
5165
5214
|
catch (error) {
|
|
5166
5215
|
console.warn(`backtestLogicPrivateService getNextCandles failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5187,7 +5236,7 @@ class BacktestLogicPrivateService {
|
|
|
5187
5236
|
// Вызываем backtest - всегда возвращает closed
|
|
5188
5237
|
let backtestResult;
|
|
5189
5238
|
try {
|
|
5190
|
-
backtestResult = await this.
|
|
5239
|
+
backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
|
|
5191
5240
|
}
|
|
5192
5241
|
catch (error) {
|
|
5193
5242
|
console.warn(`backtestLogicPrivateService backtest failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5226,7 +5275,7 @@ class BacktestLogicPrivateService {
|
|
|
5226
5275
|
}
|
|
5227
5276
|
yield backtestResult;
|
|
5228
5277
|
// Check if strategy should stop after signal is closed
|
|
5229
|
-
if (await this.
|
|
5278
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5230
5279
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
|
|
5231
5280
|
symbol,
|
|
5232
5281
|
signalId: backtestResult.signal.id,
|
|
@@ -5300,7 +5349,7 @@ const TICK_TTL = 1 * 60 * 1000 + 1;
|
|
|
5300
5349
|
class LiveLogicPrivateService {
|
|
5301
5350
|
constructor() {
|
|
5302
5351
|
this.loggerService = inject(TYPES.loggerService);
|
|
5303
|
-
this.
|
|
5352
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
5304
5353
|
this.methodContextService = inject(TYPES.methodContextService);
|
|
5305
5354
|
}
|
|
5306
5355
|
/**
|
|
@@ -5335,7 +5384,7 @@ class LiveLogicPrivateService {
|
|
|
5335
5384
|
const when = new Date();
|
|
5336
5385
|
let result;
|
|
5337
5386
|
try {
|
|
5338
|
-
result = await this.
|
|
5387
|
+
result = await this.strategyCoreService.tick(symbol, when, false);
|
|
5339
5388
|
}
|
|
5340
5389
|
catch (error) {
|
|
5341
5390
|
console.warn(`backtestLogicPrivateService tick failed when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5369,7 +5418,7 @@ class LiveLogicPrivateService {
|
|
|
5369
5418
|
previousEventTimestamp = currentTimestamp;
|
|
5370
5419
|
// Check if strategy should stop when idle (no active signal)
|
|
5371
5420
|
if (result.action === "idle") {
|
|
5372
|
-
if (await and(Promise.resolve(true), this.
|
|
5421
|
+
if (await and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
5373
5422
|
this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
|
|
5374
5423
|
symbol,
|
|
5375
5424
|
when: when.toISOString(),
|
|
@@ -5391,7 +5440,7 @@ class LiveLogicPrivateService {
|
|
|
5391
5440
|
yield result;
|
|
5392
5441
|
// Check if strategy should stop after signal is closed
|
|
5393
5442
|
if (result.action === "closed") {
|
|
5394
|
-
if (await this.
|
|
5443
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5395
5444
|
this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
|
|
5396
5445
|
symbol,
|
|
5397
5446
|
signalId: result.signal.id,
|
|
@@ -8682,8 +8731,27 @@ class HeatMarkdownService {
|
|
|
8682
8731
|
}
|
|
8683
8732
|
|
|
8684
8733
|
/**
|
|
8685
|
-
*
|
|
8686
|
-
*
|
|
8734
|
+
* Service for managing and validating exchange configurations.
|
|
8735
|
+
*
|
|
8736
|
+
* Maintains a registry of all configured exchanges and validates
|
|
8737
|
+
* their existence before operations. Uses memoization for performance.
|
|
8738
|
+
*
|
|
8739
|
+
* Key features:
|
|
8740
|
+
* - Registry management: addExchange() to register new exchanges
|
|
8741
|
+
* - Validation: validate() ensures exchange exists before use
|
|
8742
|
+
* - Memoization: validation results are cached for performance
|
|
8743
|
+
* - Listing: list() returns all registered exchanges
|
|
8744
|
+
*
|
|
8745
|
+
* @throws {Error} If duplicate exchange name is added
|
|
8746
|
+
* @throws {Error} If unknown exchange is referenced
|
|
8747
|
+
*
|
|
8748
|
+
* @example
|
|
8749
|
+
* ```typescript
|
|
8750
|
+
* const exchangeValidation = new ExchangeValidationService();
|
|
8751
|
+
* exchangeValidation.addExchange("binance", binanceSchema);
|
|
8752
|
+
* exchangeValidation.validate("binance", "backtest"); // OK
|
|
8753
|
+
* exchangeValidation.validate("unknown", "live"); // Throws error
|
|
8754
|
+
* ```
|
|
8687
8755
|
*/
|
|
8688
8756
|
class ExchangeValidationService {
|
|
8689
8757
|
constructor() {
|
|
@@ -8743,8 +8811,29 @@ class ExchangeValidationService {
|
|
|
8743
8811
|
}
|
|
8744
8812
|
|
|
8745
8813
|
/**
|
|
8746
|
-
*
|
|
8747
|
-
*
|
|
8814
|
+
* Service for managing and validating trading strategy configurations.
|
|
8815
|
+
*
|
|
8816
|
+
* Maintains a registry of all configured strategies, validates their existence
|
|
8817
|
+
* before operations, and ensures associated risk profiles are valid.
|
|
8818
|
+
* Uses memoization for performance.
|
|
8819
|
+
*
|
|
8820
|
+
* Key features:
|
|
8821
|
+
* - Registry management: addStrategy() to register new strategies
|
|
8822
|
+
* - Dual validation: validates both strategy existence and risk profile (if configured)
|
|
8823
|
+
* - Memoization: validation results are cached for performance
|
|
8824
|
+
* - Listing: list() returns all registered strategies
|
|
8825
|
+
*
|
|
8826
|
+
* @throws {Error} If duplicate strategy name is added
|
|
8827
|
+
* @throws {Error} If unknown strategy is referenced
|
|
8828
|
+
* @throws {Error} If strategy's risk profile doesn't exist
|
|
8829
|
+
*
|
|
8830
|
+
* @example
|
|
8831
|
+
* ```typescript
|
|
8832
|
+
* const strategyValidation = new StrategyValidationService();
|
|
8833
|
+
* strategyValidation.addStrategy("momentum-btc", { ...schema, riskName: "conservative" });
|
|
8834
|
+
* strategyValidation.validate("momentum-btc", "backtest"); // Validates strategy + risk
|
|
8835
|
+
* strategyValidation.validate("unknown", "live"); // Throws error
|
|
8836
|
+
* ```
|
|
8748
8837
|
*/
|
|
8749
8838
|
class StrategyValidationService {
|
|
8750
8839
|
constructor() {
|
|
@@ -8815,8 +8904,27 @@ class StrategyValidationService {
|
|
|
8815
8904
|
}
|
|
8816
8905
|
|
|
8817
8906
|
/**
|
|
8818
|
-
*
|
|
8819
|
-
*
|
|
8907
|
+
* Service for managing and validating frame (timeframe) configurations.
|
|
8908
|
+
*
|
|
8909
|
+
* Maintains a registry of all configured frames and validates
|
|
8910
|
+
* their existence before operations. Uses memoization for performance.
|
|
8911
|
+
*
|
|
8912
|
+
* Key features:
|
|
8913
|
+
* - Registry management: addFrame() to register new timeframes
|
|
8914
|
+
* - Validation: validate() ensures frame exists before use
|
|
8915
|
+
* - Memoization: validation results are cached for performance
|
|
8916
|
+
* - Listing: list() returns all registered frames
|
|
8917
|
+
*
|
|
8918
|
+
* @throws {Error} If duplicate frame name is added
|
|
8919
|
+
* @throws {Error} If unknown frame is referenced
|
|
8920
|
+
*
|
|
8921
|
+
* @example
|
|
8922
|
+
* ```typescript
|
|
8923
|
+
* const frameValidation = new FrameValidationService();
|
|
8924
|
+
* frameValidation.addFrame("2024-Q1", frameSchema);
|
|
8925
|
+
* frameValidation.validate("2024-Q1", "backtest"); // OK
|
|
8926
|
+
* frameValidation.validate("unknown", "live"); // Throws error
|
|
8927
|
+
* ```
|
|
8820
8928
|
*/
|
|
8821
8929
|
class FrameValidationService {
|
|
8822
8930
|
constructor() {
|
|
@@ -8876,8 +8984,29 @@ class FrameValidationService {
|
|
|
8876
8984
|
}
|
|
8877
8985
|
|
|
8878
8986
|
/**
|
|
8879
|
-
*
|
|
8880
|
-
*
|
|
8987
|
+
* Service for managing and validating walker (parameter sweep) configurations.
|
|
8988
|
+
*
|
|
8989
|
+
* Maintains a registry of all configured walkers and validates
|
|
8990
|
+
* their existence before operations. Uses memoization for performance.
|
|
8991
|
+
*
|
|
8992
|
+
* Walkers define parameter ranges for optimization and hyperparameter tuning.
|
|
8993
|
+
*
|
|
8994
|
+
* Key features:
|
|
8995
|
+
* - Registry management: addWalker() to register new walker configurations
|
|
8996
|
+
* - Validation: validate() ensures walker exists before use
|
|
8997
|
+
* - Memoization: validation results are cached for performance
|
|
8998
|
+
* - Listing: list() returns all registered walkers
|
|
8999
|
+
*
|
|
9000
|
+
* @throws {Error} If duplicate walker name is added
|
|
9001
|
+
* @throws {Error} If unknown walker is referenced
|
|
9002
|
+
*
|
|
9003
|
+
* @example
|
|
9004
|
+
* ```typescript
|
|
9005
|
+
* const walkerValidation = new WalkerValidationService();
|
|
9006
|
+
* walkerValidation.addWalker("rsi-sweep", walkerSchema);
|
|
9007
|
+
* walkerValidation.validate("rsi-sweep", "optimizer"); // OK
|
|
9008
|
+
* walkerValidation.validate("unknown", "optimizer"); // Throws error
|
|
9009
|
+
* ```
|
|
8881
9010
|
*/
|
|
8882
9011
|
class WalkerValidationService {
|
|
8883
9012
|
constructor() {
|
|
@@ -8937,8 +9066,27 @@ class WalkerValidationService {
|
|
|
8937
9066
|
}
|
|
8938
9067
|
|
|
8939
9068
|
/**
|
|
8940
|
-
*
|
|
8941
|
-
*
|
|
9069
|
+
* Service for managing and validating position sizing configurations.
|
|
9070
|
+
*
|
|
9071
|
+
* Maintains a registry of all configured sizing strategies and validates
|
|
9072
|
+
* their existence before operations. Uses memoization for performance.
|
|
9073
|
+
*
|
|
9074
|
+
* Key features:
|
|
9075
|
+
* - Registry management: addSizing() to register new sizing strategies
|
|
9076
|
+
* - Validation: validate() ensures sizing strategy exists before use
|
|
9077
|
+
* - Memoization: validation results are cached for performance
|
|
9078
|
+
* - Listing: list() returns all registered sizing strategies
|
|
9079
|
+
*
|
|
9080
|
+
* @throws {Error} If duplicate sizing name is added
|
|
9081
|
+
* @throws {Error} If unknown sizing strategy is referenced
|
|
9082
|
+
*
|
|
9083
|
+
* @example
|
|
9084
|
+
* ```typescript
|
|
9085
|
+
* const sizingValidation = new SizingValidationService();
|
|
9086
|
+
* sizingValidation.addSizing("fixed-1000", fixedSizingSchema);
|
|
9087
|
+
* sizingValidation.validate("fixed-1000", "strategy-1"); // OK
|
|
9088
|
+
* sizingValidation.validate("unknown", "strategy-2"); // Throws error
|
|
9089
|
+
* ```
|
|
8942
9090
|
*/
|
|
8943
9091
|
class SizingValidationService {
|
|
8944
9092
|
constructor() {
|
|
@@ -9003,8 +9151,27 @@ class SizingValidationService {
|
|
|
9003
9151
|
}
|
|
9004
9152
|
|
|
9005
9153
|
/**
|
|
9006
|
-
*
|
|
9007
|
-
*
|
|
9154
|
+
* Service for managing and validating risk management configurations.
|
|
9155
|
+
*
|
|
9156
|
+
* Maintains a registry of all configured risk profiles and validates
|
|
9157
|
+
* their existence before operations. Uses memoization for performance.
|
|
9158
|
+
*
|
|
9159
|
+
* Key features:
|
|
9160
|
+
* - Registry management: addRisk() to register new risk profiles
|
|
9161
|
+
* - Validation: validate() ensures risk profile exists before use
|
|
9162
|
+
* - Memoization: validation results are cached by riskName:source for performance
|
|
9163
|
+
* - Listing: list() returns all registered risk profiles
|
|
9164
|
+
*
|
|
9165
|
+
* @throws {Error} If duplicate risk name is added
|
|
9166
|
+
* @throws {Error} If unknown risk profile is referenced
|
|
9167
|
+
*
|
|
9168
|
+
* @example
|
|
9169
|
+
* ```typescript
|
|
9170
|
+
* const riskValidation = new RiskValidationService();
|
|
9171
|
+
* riskValidation.addRisk("conservative", conservativeSchema);
|
|
9172
|
+
* riskValidation.validate("conservative", "strategy-1"); // OK
|
|
9173
|
+
* riskValidation.validate("unknown", "strategy-2"); // Throws error
|
|
9174
|
+
* ```
|
|
9008
9175
|
*/
|
|
9009
9176
|
class RiskValidationService {
|
|
9010
9177
|
constructor() {
|
|
@@ -11556,6 +11723,133 @@ class OutlineMarkdownService {
|
|
|
11556
11723
|
}
|
|
11557
11724
|
}
|
|
11558
11725
|
|
|
11726
|
+
/**
|
|
11727
|
+
* Service for validating GLOBAL_CONFIG parameters to ensure mathematical correctness
|
|
11728
|
+
* and prevent unprofitable trading configurations.
|
|
11729
|
+
*
|
|
11730
|
+
* Performs comprehensive validation on:
|
|
11731
|
+
* - **Percentage parameters**: Slippage, fees, and profit margins must be non-negative
|
|
11732
|
+
* - **Economic viability**: Ensures CC_MIN_TAKEPROFIT_DISTANCE_PERCENT covers all trading costs
|
|
11733
|
+
* (slippage + fees) to guarantee profitable trades when TakeProfit is hit
|
|
11734
|
+
* - **Range constraints**: Validates MIN < MAX relationships (e.g., StopLoss distances)
|
|
11735
|
+
* - **Time-based parameters**: Ensures positive integer values for timeouts and lifetimes
|
|
11736
|
+
* - **Candle parameters**: Validates retry counts, delays, and anomaly detection thresholds
|
|
11737
|
+
*
|
|
11738
|
+
* @throws {Error} If any validation fails, throws with detailed breakdown of all errors
|
|
11739
|
+
*
|
|
11740
|
+
* @example
|
|
11741
|
+
* ```typescript
|
|
11742
|
+
* const validator = new ConfigValidationService();
|
|
11743
|
+
* validator.validate(); // Throws if config is invalid
|
|
11744
|
+
* ```
|
|
11745
|
+
*
|
|
11746
|
+
* @example Validation failure output:
|
|
11747
|
+
* ```
|
|
11748
|
+
* GLOBAL_CONFIG validation failed:
|
|
11749
|
+
* 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (0.3%) is too low to cover trading costs.
|
|
11750
|
+
* Required minimum: 0.40%
|
|
11751
|
+
* Breakdown:
|
|
11752
|
+
* - Slippage effect: 0.20% (0.1% × 2 transactions)
|
|
11753
|
+
* - Fees: 0.20% (0.1% × 2 transactions)
|
|
11754
|
+
* All TakeProfit signals will be unprofitable with current settings!
|
|
11755
|
+
* ```
|
|
11756
|
+
*/
|
|
11757
|
+
class ConfigValidationService {
|
|
11758
|
+
constructor() {
|
|
11759
|
+
/**
|
|
11760
|
+
* @private
|
|
11761
|
+
* @readonly
|
|
11762
|
+
* Injected logger service instance
|
|
11763
|
+
*/
|
|
11764
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
11765
|
+
/**
|
|
11766
|
+
* Validates GLOBAL_CONFIG parameters for mathematical correctness.
|
|
11767
|
+
*
|
|
11768
|
+
* Checks:
|
|
11769
|
+
* 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must cover slippage + fees
|
|
11770
|
+
* 2. All percentage values must be positive
|
|
11771
|
+
* 3. Time/count values must be positive integers
|
|
11772
|
+
*
|
|
11773
|
+
* @throws Error if configuration is invalid
|
|
11774
|
+
*/
|
|
11775
|
+
this.validate = () => {
|
|
11776
|
+
this.loggerService.log("configValidationService validate");
|
|
11777
|
+
const errors = [];
|
|
11778
|
+
// Validate slippage and fee percentages
|
|
11779
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE) || GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE < 0) {
|
|
11780
|
+
errors.push(`CC_PERCENT_SLIPPAGE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}`);
|
|
11781
|
+
}
|
|
11782
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_FEE) || GLOBAL_CONFIG.CC_PERCENT_FEE < 0) {
|
|
11783
|
+
errors.push(`CC_PERCENT_FEE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_FEE}`);
|
|
11784
|
+
}
|
|
11785
|
+
// Calculate minimum required TP distance to cover costs
|
|
11786
|
+
const slippageEffect = GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE * 2; // Applied twice (entry + exit)
|
|
11787
|
+
const feesTotal = GLOBAL_CONFIG.CC_PERCENT_FEE * 2; // Applied twice (entry + exit)
|
|
11788
|
+
const minRequiredTpDistance = slippageEffect + feesTotal;
|
|
11789
|
+
// Validate CC_MIN_TAKEPROFIT_DISTANCE_PERCENT
|
|
11790
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT <= 0) {
|
|
11791
|
+
errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}`);
|
|
11792
|
+
}
|
|
11793
|
+
else if (GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT < minRequiredTpDistance) {
|
|
11794
|
+
errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}%) is too low to cover trading costs.\n` +
|
|
11795
|
+
` Required minimum: ${minRequiredTpDistance.toFixed(2)}%\n` +
|
|
11796
|
+
` Breakdown:\n` +
|
|
11797
|
+
` - Slippage effect: ${slippageEffect.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}% × 2 transactions)\n` +
|
|
11798
|
+
` - Fees: ${feesTotal.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_FEE}% × 2 transactions)\n` +
|
|
11799
|
+
` All TakeProfit signals will be unprofitable with current settings!`);
|
|
11800
|
+
}
|
|
11801
|
+
// Validate CC_MIN_STOPLOSS_DISTANCE_PERCENT
|
|
11802
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT <= 0) {
|
|
11803
|
+
errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}`);
|
|
11804
|
+
}
|
|
11805
|
+
// Validate CC_MAX_STOPLOSS_DISTANCE_PERCENT
|
|
11806
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT <= 0) {
|
|
11807
|
+
errors.push(`CC_MAX_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}`);
|
|
11808
|
+
}
|
|
11809
|
+
// Validate that MIN < MAX for StopLoss
|
|
11810
|
+
if (Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) &&
|
|
11811
|
+
Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) &&
|
|
11812
|
+
GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT >= GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) {
|
|
11813
|
+
errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}%) must be less than ` +
|
|
11814
|
+
`CC_MAX_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}%)`);
|
|
11815
|
+
}
|
|
11816
|
+
// Validate time-based parameters
|
|
11817
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES) || GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES <= 0) {
|
|
11818
|
+
errors.push(`CC_SCHEDULE_AWAIT_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES}`);
|
|
11819
|
+
}
|
|
11820
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES) || GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES <= 0) {
|
|
11821
|
+
errors.push(`CC_MAX_SIGNAL_LIFETIME_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES}`);
|
|
11822
|
+
}
|
|
11823
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS) || GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS <= 0) {
|
|
11824
|
+
errors.push(`CC_MAX_SIGNAL_GENERATION_SECONDS must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS}`);
|
|
11825
|
+
}
|
|
11826
|
+
// Validate candle-based parameters
|
|
11827
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT) || GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT <= 0) {
|
|
11828
|
+
errors.push(`CC_AVG_PRICE_CANDLES_COUNT must be a positive integer, got ${GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT}`);
|
|
11829
|
+
}
|
|
11830
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT < 0) {
|
|
11831
|
+
errors.push(`CC_GET_CANDLES_RETRY_COUNT must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT}`);
|
|
11832
|
+
}
|
|
11833
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS < 0) {
|
|
11834
|
+
errors.push(`CC_GET_CANDLES_RETRY_DELAY_MS must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS}`);
|
|
11835
|
+
}
|
|
11836
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR) || GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR <= 0) {
|
|
11837
|
+
errors.push(`CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR}`);
|
|
11838
|
+
}
|
|
11839
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN) || GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN <= 0) {
|
|
11840
|
+
errors.push(`CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN}`);
|
|
11841
|
+
}
|
|
11842
|
+
// Throw aggregated errors if any
|
|
11843
|
+
if (errors.length > 0) {
|
|
11844
|
+
const errorMessage = `GLOBAL_CONFIG validation failed:\n${errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`;
|
|
11845
|
+
this.loggerService.warn(errorMessage);
|
|
11846
|
+
throw new Error(errorMessage);
|
|
11847
|
+
}
|
|
11848
|
+
this.loggerService.log("configValidationService validation passed");
|
|
11849
|
+
};
|
|
11850
|
+
}
|
|
11851
|
+
}
|
|
11852
|
+
|
|
11559
11853
|
{
|
|
11560
11854
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
11561
11855
|
}
|
|
@@ -11582,9 +11876,11 @@ class OutlineMarkdownService {
|
|
|
11582
11876
|
provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
|
|
11583
11877
|
}
|
|
11584
11878
|
{
|
|
11585
|
-
provide(TYPES.
|
|
11586
|
-
provide(TYPES.
|
|
11587
|
-
provide(TYPES.
|
|
11879
|
+
provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
|
|
11880
|
+
provide(TYPES.strategyCoreService, () => new StrategyCoreService());
|
|
11881
|
+
provide(TYPES.frameCoreService, () => new FrameCoreService());
|
|
11882
|
+
}
|
|
11883
|
+
{
|
|
11588
11884
|
provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
|
|
11589
11885
|
provide(TYPES.riskGlobalService, () => new RiskGlobalService());
|
|
11590
11886
|
provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
|
|
@@ -11623,6 +11919,7 @@ class OutlineMarkdownService {
|
|
|
11623
11919
|
provide(TYPES.sizingValidationService, () => new SizingValidationService());
|
|
11624
11920
|
provide(TYPES.riskValidationService, () => new RiskValidationService());
|
|
11625
11921
|
provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
|
|
11922
|
+
provide(TYPES.configValidationService, () => new ConfigValidationService());
|
|
11626
11923
|
}
|
|
11627
11924
|
{
|
|
11628
11925
|
provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
|
|
@@ -11653,10 +11950,12 @@ const schemaServices = {
|
|
|
11653
11950
|
riskSchemaService: inject(TYPES.riskSchemaService),
|
|
11654
11951
|
optimizerSchemaService: inject(TYPES.optimizerSchemaService),
|
|
11655
11952
|
};
|
|
11953
|
+
const coreServices = {
|
|
11954
|
+
exchangeCoreService: inject(TYPES.exchangeCoreService),
|
|
11955
|
+
strategyCoreService: inject(TYPES.strategyCoreService),
|
|
11956
|
+
frameCoreService: inject(TYPES.frameCoreService),
|
|
11957
|
+
};
|
|
11656
11958
|
const globalServices = {
|
|
11657
|
-
exchangeGlobalService: inject(TYPES.exchangeGlobalService),
|
|
11658
|
-
strategyGlobalService: inject(TYPES.strategyGlobalService),
|
|
11659
|
-
frameGlobalService: inject(TYPES.frameGlobalService),
|
|
11660
11959
|
sizingGlobalService: inject(TYPES.sizingGlobalService),
|
|
11661
11960
|
riskGlobalService: inject(TYPES.riskGlobalService),
|
|
11662
11961
|
optimizerGlobalService: inject(TYPES.optimizerGlobalService),
|
|
@@ -11695,6 +11994,7 @@ const validationServices = {
|
|
|
11695
11994
|
sizingValidationService: inject(TYPES.sizingValidationService),
|
|
11696
11995
|
riskValidationService: inject(TYPES.riskValidationService),
|
|
11697
11996
|
optimizerValidationService: inject(TYPES.optimizerValidationService),
|
|
11997
|
+
configValidationService: inject(TYPES.configValidationService),
|
|
11698
11998
|
};
|
|
11699
11999
|
const templateServices = {
|
|
11700
12000
|
optimizerTemplateService: inject(TYPES.optimizerTemplateService),
|
|
@@ -11704,6 +12004,7 @@ const backtest = {
|
|
|
11704
12004
|
...contextServices,
|
|
11705
12005
|
...connectionServices,
|
|
11706
12006
|
...schemaServices,
|
|
12007
|
+
...coreServices,
|
|
11707
12008
|
...globalServices,
|
|
11708
12009
|
...commandServices,
|
|
11709
12010
|
...logicPrivateServices,
|
|
@@ -11732,12 +12033,13 @@ var backtest$1 = backtest;
|
|
|
11732
12033
|
* });
|
|
11733
12034
|
* ```
|
|
11734
12035
|
*/
|
|
11735
|
-
|
|
12036
|
+
function setLogger(logger) {
|
|
11736
12037
|
backtest$1.loggerService.setLogger(logger);
|
|
11737
12038
|
}
|
|
11738
12039
|
/**
|
|
11739
12040
|
* Sets global configuration parameters for the framework.
|
|
11740
12041
|
* @param config - Partial configuration object to override default settings
|
|
12042
|
+
* @param _unsafe - Skip config validations - required for testbed
|
|
11741
12043
|
*
|
|
11742
12044
|
* @example
|
|
11743
12045
|
* ```typescript
|
|
@@ -11746,8 +12048,51 @@ async function setLogger(logger) {
|
|
|
11746
12048
|
* });
|
|
11747
12049
|
* ```
|
|
11748
12050
|
*/
|
|
11749
|
-
|
|
11750
|
-
Object.assign(
|
|
12051
|
+
function setConfig(config, _unsafe) {
|
|
12052
|
+
const prevConfig = Object.assign({}, GLOBAL_CONFIG);
|
|
12053
|
+
try {
|
|
12054
|
+
Object.assign(GLOBAL_CONFIG, config);
|
|
12055
|
+
!_unsafe && backtest$1.configValidationService.validate();
|
|
12056
|
+
}
|
|
12057
|
+
catch (error) {
|
|
12058
|
+
console.warn(`backtest-kit setConfig failed: ${getErrorMessage(error)}`, config);
|
|
12059
|
+
Object.assign(GLOBAL_CONFIG, prevConfig);
|
|
12060
|
+
throw error;
|
|
12061
|
+
}
|
|
12062
|
+
}
|
|
12063
|
+
/**
|
|
12064
|
+
* Retrieves a copy of the current global configuration.
|
|
12065
|
+
*
|
|
12066
|
+
* Returns a shallow copy of the current GLOBAL_CONFIG to prevent accidental mutations.
|
|
12067
|
+
* Use this to inspect the current configuration state without modifying it.
|
|
12068
|
+
*
|
|
12069
|
+
* @returns {GlobalConfig} A copy of the current global configuration object
|
|
12070
|
+
*
|
|
12071
|
+
* @example
|
|
12072
|
+
* ```typescript
|
|
12073
|
+
* const currentConfig = getConfig();
|
|
12074
|
+
* console.log(currentConfig.CC_SCHEDULE_AWAIT_MINUTES);
|
|
12075
|
+
* ```
|
|
12076
|
+
*/
|
|
12077
|
+
function getConfig() {
|
|
12078
|
+
return Object.assign({}, GLOBAL_CONFIG);
|
|
12079
|
+
}
|
|
12080
|
+
/**
|
|
12081
|
+
* Retrieves the default configuration object for the framework.
|
|
12082
|
+
*
|
|
12083
|
+
* Returns a reference to the default configuration with all preset values.
|
|
12084
|
+
* Use this to see what configuration options are available and their default values.
|
|
12085
|
+
*
|
|
12086
|
+
* @returns {GlobalConfig} The default configuration object
|
|
12087
|
+
*
|
|
12088
|
+
* @example
|
|
12089
|
+
* ```typescript
|
|
12090
|
+
* const defaultConfig = getDefaultConfig();
|
|
12091
|
+
* console.log(defaultConfig.CC_SCHEDULE_AWAIT_MINUTES);
|
|
12092
|
+
* ```
|
|
12093
|
+
*/
|
|
12094
|
+
function getDefaultConfig() {
|
|
12095
|
+
return DEFAULT_CONFIG;
|
|
11751
12096
|
}
|
|
11752
12097
|
|
|
11753
12098
|
const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
|
|
@@ -13557,7 +13902,7 @@ class BacktestInstance {
|
|
|
13557
13902
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
13558
13903
|
}
|
|
13559
13904
|
{
|
|
13560
|
-
backtest$1.
|
|
13905
|
+
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
13561
13906
|
}
|
|
13562
13907
|
{
|
|
13563
13908
|
const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
@@ -13592,8 +13937,8 @@ class BacktestInstance {
|
|
|
13592
13937
|
});
|
|
13593
13938
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
13594
13939
|
return () => {
|
|
13595
|
-
backtest$1.
|
|
13596
|
-
backtest$1.
|
|
13940
|
+
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, true);
|
|
13941
|
+
backtest$1.strategyCoreService
|
|
13597
13942
|
.getPendingSignal(symbol, context.strategyName)
|
|
13598
13943
|
.then(async (pendingSignal) => {
|
|
13599
13944
|
if (pendingSignal) {
|
|
@@ -13634,7 +13979,7 @@ class BacktestInstance {
|
|
|
13634
13979
|
symbol,
|
|
13635
13980
|
strategyName,
|
|
13636
13981
|
});
|
|
13637
|
-
await backtest$1.
|
|
13982
|
+
await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
|
|
13638
13983
|
};
|
|
13639
13984
|
/**
|
|
13640
13985
|
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
@@ -14049,7 +14394,7 @@ class LiveInstance {
|
|
|
14049
14394
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
14050
14395
|
}
|
|
14051
14396
|
{
|
|
14052
|
-
backtest$1.
|
|
14397
|
+
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
14053
14398
|
}
|
|
14054
14399
|
{
|
|
14055
14400
|
const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
@@ -14084,8 +14429,8 @@ class LiveInstance {
|
|
|
14084
14429
|
});
|
|
14085
14430
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
14086
14431
|
return () => {
|
|
14087
|
-
backtest$1.
|
|
14088
|
-
backtest$1.
|
|
14432
|
+
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, false);
|
|
14433
|
+
backtest$1.strategyCoreService
|
|
14089
14434
|
.getPendingSignal(symbol, context.strategyName)
|
|
14090
14435
|
.then(async (pendingSignal) => {
|
|
14091
14436
|
if (pendingSignal) {
|
|
@@ -14126,7 +14471,7 @@ class LiveInstance {
|
|
|
14126
14471
|
symbol,
|
|
14127
14472
|
strategyName,
|
|
14128
14473
|
});
|
|
14129
|
-
await backtest$1.
|
|
14474
|
+
await backtest$1.strategyCoreService.stop({ symbol, strategyName }, false);
|
|
14130
14475
|
};
|
|
14131
14476
|
/**
|
|
14132
14477
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
@@ -14807,7 +15152,7 @@ class WalkerInstance {
|
|
|
14807
15152
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
|
|
14808
15153
|
}
|
|
14809
15154
|
{
|
|
14810
|
-
backtest$1.
|
|
15155
|
+
backtest$1.strategyCoreService.clear({ symbol, strategyName });
|
|
14811
15156
|
}
|
|
14812
15157
|
{
|
|
14813
15158
|
const { riskName } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -14847,7 +15192,7 @@ class WalkerInstance {
|
|
|
14847
15192
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
14848
15193
|
return () => {
|
|
14849
15194
|
for (const strategyName of walkerSchema.strategies) {
|
|
14850
|
-
backtest$1.
|
|
15195
|
+
backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
|
|
14851
15196
|
walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
|
|
14852
15197
|
}
|
|
14853
15198
|
if (!this._isDone) {
|
|
@@ -14893,7 +15238,7 @@ class WalkerInstance {
|
|
|
14893
15238
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
14894
15239
|
for (const strategyName of walkerSchema.strategies) {
|
|
14895
15240
|
await walkerStopSubject.next({ symbol, strategyName, walkerName });
|
|
14896
|
-
await backtest$1.
|
|
15241
|
+
await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
|
|
14897
15242
|
}
|
|
14898
15243
|
};
|
|
14899
15244
|
/**
|
|
@@ -15836,4 +16181,4 @@ class ConstantUtils {
|
|
|
15836
16181
|
*/
|
|
15837
16182
|
const Constant = new ConstantUtils();
|
|
15838
16183
|
|
|
15839
|
-
export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getDate, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
|
|
16184
|
+
export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getConfig, getDate, getDefaultConfig, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
|