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.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var functoolsKit = require('functools-kit');
|
|
3
4
|
var diKit = require('di-kit');
|
|
4
5
|
var diScoped = require('di-scoped');
|
|
5
|
-
var functoolsKit = require('functools-kit');
|
|
6
6
|
var fs = require('fs/promises');
|
|
7
7
|
var path = require('path');
|
|
8
8
|
var crypto = require('crypto');
|
|
@@ -20,12 +20,32 @@ const GLOBAL_CONFIG = {
|
|
|
20
20
|
* Default: 5 candles (last 5 minutes when using 1m interval)
|
|
21
21
|
*/
|
|
22
22
|
CC_AVG_PRICE_CANDLES_COUNT: 5,
|
|
23
|
+
/**
|
|
24
|
+
* Slippage percentage applied to entry and exit prices.
|
|
25
|
+
* Simulates market impact and order book depth.
|
|
26
|
+
* Applied twice (entry and exit) for realistic execution simulation.
|
|
27
|
+
* Default: 0.1% per transaction
|
|
28
|
+
*/
|
|
29
|
+
CC_PERCENT_SLIPPAGE: 0.1,
|
|
30
|
+
/**
|
|
31
|
+
* Fee percentage charged per transaction.
|
|
32
|
+
* Applied twice (entry and exit) for total fee calculation.
|
|
33
|
+
* Default: 0.1% per transaction (total 0.2%)
|
|
34
|
+
*/
|
|
35
|
+
CC_PERCENT_FEE: 0.1,
|
|
23
36
|
/**
|
|
24
37
|
* Minimum TakeProfit distance from priceOpen (percentage)
|
|
25
|
-
* Must be greater than
|
|
26
|
-
*
|
|
38
|
+
* Must be greater than (slippage + fees) to ensure profitable trades
|
|
39
|
+
*
|
|
40
|
+
* Calculation:
|
|
41
|
+
* - Slippage effect: ~0.2% (0.1% × 2 transactions)
|
|
42
|
+
* - Fees: 0.2% (0.1% × 2 transactions)
|
|
43
|
+
* - Minimum profit buffer: 0.1%
|
|
44
|
+
* - Total: 0.5%
|
|
45
|
+
*
|
|
46
|
+
* Default: 0.5% (covers all costs + minimum profit margin)
|
|
27
47
|
*/
|
|
28
|
-
CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.
|
|
48
|
+
CC_MIN_TAKEPROFIT_DISTANCE_PERCENT: 0.5,
|
|
29
49
|
/**
|
|
30
50
|
* Minimum StopLoss distance from priceOpen (percentage)
|
|
31
51
|
* Prevents signals from being immediately stopped out due to price volatility
|
|
@@ -92,6 +112,7 @@ const GLOBAL_CONFIG = {
|
|
|
92
112
|
*/
|
|
93
113
|
CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: 5,
|
|
94
114
|
};
|
|
115
|
+
const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
|
|
95
116
|
|
|
96
117
|
const { init, inject, provide } = diKit.createActivator("backtest");
|
|
97
118
|
|
|
@@ -146,10 +167,12 @@ const schemaServices$1 = {
|
|
|
146
167
|
riskSchemaService: Symbol('riskSchemaService'),
|
|
147
168
|
optimizerSchemaService: Symbol('optimizerSchemaService'),
|
|
148
169
|
};
|
|
170
|
+
const coreServices$1 = {
|
|
171
|
+
exchangeCoreService: Symbol('exchangeCoreService'),
|
|
172
|
+
strategyCoreService: Symbol('strategyCoreService'),
|
|
173
|
+
frameCoreService: Symbol('frameCoreService'),
|
|
174
|
+
};
|
|
149
175
|
const globalServices$1 = {
|
|
150
|
-
exchangeGlobalService: Symbol('exchangeGlobalService'),
|
|
151
|
-
strategyGlobalService: Symbol('strategyGlobalService'),
|
|
152
|
-
frameGlobalService: Symbol('frameGlobalService'),
|
|
153
176
|
sizingGlobalService: Symbol('sizingGlobalService'),
|
|
154
177
|
riskGlobalService: Symbol('riskGlobalService'),
|
|
155
178
|
optimizerGlobalService: Symbol('optimizerGlobalService'),
|
|
@@ -188,6 +211,7 @@ const validationServices$1 = {
|
|
|
188
211
|
sizingValidationService: Symbol('sizingValidationService'),
|
|
189
212
|
riskValidationService: Symbol('riskValidationService'),
|
|
190
213
|
optimizerValidationService: Symbol('optimizerValidationService'),
|
|
214
|
+
configValidationService: Symbol('configValidationService'),
|
|
191
215
|
};
|
|
192
216
|
const templateServices$1 = {
|
|
193
217
|
optimizerTemplateService: Symbol('optimizerTemplateService'),
|
|
@@ -197,6 +221,7 @@ const TYPES = {
|
|
|
197
221
|
...contextServices$1,
|
|
198
222
|
...connectionServices$1,
|
|
199
223
|
...schemaServices$1,
|
|
224
|
+
...coreServices$1,
|
|
200
225
|
...globalServices$1,
|
|
201
226
|
...commandServices$1,
|
|
202
227
|
...logicPrivateServices$1,
|
|
@@ -717,16 +742,6 @@ class ExchangeConnectionService {
|
|
|
717
742
|
}
|
|
718
743
|
}
|
|
719
744
|
|
|
720
|
-
/**
|
|
721
|
-
* Slippage percentage applied to entry and exit prices.
|
|
722
|
-
* Simulates market impact and order book depth.
|
|
723
|
-
*/
|
|
724
|
-
const PERCENT_SLIPPAGE = 0.1;
|
|
725
|
-
/**
|
|
726
|
-
* Fee percentage charged per transaction.
|
|
727
|
-
* Applied twice (entry and exit) for total fee calculation.
|
|
728
|
-
*/
|
|
729
|
-
const PERCENT_FEE = 0.1;
|
|
730
745
|
/**
|
|
731
746
|
* Calculates profit/loss for a closed signal with slippage and fees.
|
|
732
747
|
*
|
|
@@ -762,16 +777,16 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
762
777
|
let priceCloseWithSlippage;
|
|
763
778
|
if (signal.position === "long") {
|
|
764
779
|
// LONG: покупаем дороже, продаем дешевле
|
|
765
|
-
priceOpenWithSlippage = priceOpen * (1 +
|
|
766
|
-
priceCloseWithSlippage = priceClose * (1 -
|
|
780
|
+
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
781
|
+
priceCloseWithSlippage = priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
767
782
|
}
|
|
768
783
|
else {
|
|
769
784
|
// SHORT: продаем дешевле, покупаем дороже
|
|
770
|
-
priceOpenWithSlippage = priceOpen * (1 -
|
|
771
|
-
priceCloseWithSlippage = priceClose * (1 +
|
|
785
|
+
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
786
|
+
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
772
787
|
}
|
|
773
788
|
// Применяем комиссию дважды (при открытии и закрытии)
|
|
774
|
-
const totalFee =
|
|
789
|
+
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE * 2;
|
|
775
790
|
let pnlPercentage;
|
|
776
791
|
if (signal.position === "long") {
|
|
777
792
|
// LONG: прибыль при росте цены
|
|
@@ -1989,15 +2004,6 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
1989
2004
|
self._lastSignalTimestamp = currentTime;
|
|
1990
2005
|
}
|
|
1991
2006
|
const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
|
|
1992
|
-
if (await functoolsKit.not(self.params.risk.checkSignal({
|
|
1993
|
-
symbol: self.params.execution.context.symbol,
|
|
1994
|
-
strategyName: self.params.method.context.strategyName,
|
|
1995
|
-
exchangeName: self.params.method.context.exchangeName,
|
|
1996
|
-
currentPrice,
|
|
1997
|
-
timestamp: currentTime,
|
|
1998
|
-
}))) {
|
|
1999
|
-
return null;
|
|
2000
|
-
}
|
|
2001
2007
|
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
2002
2008
|
const signal = await Promise.race([
|
|
2003
2009
|
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
|
|
@@ -2012,6 +2018,16 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
2012
2018
|
if (self._isStopped) {
|
|
2013
2019
|
return null;
|
|
2014
2020
|
}
|
|
2021
|
+
if (await functoolsKit.not(self.params.risk.checkSignal({
|
|
2022
|
+
pendingSignal: signal,
|
|
2023
|
+
symbol: self.params.execution.context.symbol,
|
|
2024
|
+
strategyName: self.params.method.context.strategyName,
|
|
2025
|
+
exchangeName: self.params.method.context.exchangeName,
|
|
2026
|
+
currentPrice,
|
|
2027
|
+
timestamp: currentTime,
|
|
2028
|
+
}))) {
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2015
2031
|
// Если priceOpen указан - проверяем нужно ли ждать активации или открыть сразу
|
|
2016
2032
|
if (signal.priceOpen !== undefined) {
|
|
2017
2033
|
// КРИТИЧЕСКАЯ ПРОВЕРКА: достигнут ли priceOpen?
|
|
@@ -2243,6 +2259,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
|
|
|
2243
2259
|
});
|
|
2244
2260
|
if (await functoolsKit.not(self.params.risk.checkSignal({
|
|
2245
2261
|
symbol: self.params.execution.context.symbol,
|
|
2262
|
+
pendingSignal: scheduled,
|
|
2246
2263
|
strategyName: self.params.method.context.strategyName,
|
|
2247
2264
|
exchangeName: self.params.method.context.exchangeName,
|
|
2248
2265
|
currentPrice: scheduled.priceOpen,
|
|
@@ -2326,6 +2343,7 @@ const OPEN_NEW_SCHEDULED_SIGNAL_FN = async (self, signal) => {
|
|
|
2326
2343
|
};
|
|
2327
2344
|
const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
|
|
2328
2345
|
if (await functoolsKit.not(self.params.risk.checkSignal({
|
|
2346
|
+
pendingSignal: signal,
|
|
2329
2347
|
symbol: self.params.execution.context.symbol,
|
|
2330
2348
|
strategyName: self.params.method.context.strategyName,
|
|
2331
2349
|
exchangeName: self.params.method.context.exchangeName,
|
|
@@ -2552,6 +2570,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
|
|
|
2552
2570
|
pendingAt: activationTime,
|
|
2553
2571
|
});
|
|
2554
2572
|
if (await functoolsKit.not(self.params.risk.checkSignal({
|
|
2573
|
+
pendingSignal: scheduled,
|
|
2555
2574
|
symbol: self.params.execution.context.symbol,
|
|
2556
2575
|
strategyName: self.params.method.context.strategyName,
|
|
2557
2576
|
exchangeName: self.params.method.context.exchangeName,
|
|
@@ -4013,7 +4032,7 @@ class RiskConnectionService {
|
|
|
4013
4032
|
}
|
|
4014
4033
|
}
|
|
4015
4034
|
|
|
4016
|
-
const METHOD_NAME_VALIDATE$1 = "
|
|
4035
|
+
const METHOD_NAME_VALIDATE$1 = "exchangeCoreService validate";
|
|
4017
4036
|
/**
|
|
4018
4037
|
* Global service for exchange operations with execution context injection.
|
|
4019
4038
|
*
|
|
@@ -4022,7 +4041,7 @@ const METHOD_NAME_VALIDATE$1 = "exchangeGlobalService validate";
|
|
|
4022
4041
|
*
|
|
4023
4042
|
* Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
|
|
4024
4043
|
*/
|
|
4025
|
-
class
|
|
4044
|
+
class ExchangeCoreService {
|
|
4026
4045
|
constructor() {
|
|
4027
4046
|
this.loggerService = inject(TYPES.loggerService);
|
|
4028
4047
|
this.exchangeConnectionService = inject(TYPES.exchangeConnectionService);
|
|
@@ -4052,13 +4071,16 @@ class ExchangeGlobalService {
|
|
|
4052
4071
|
* @returns Promise resolving to array of candles
|
|
4053
4072
|
*/
|
|
4054
4073
|
this.getCandles = async (symbol, interval, limit, when, backtest) => {
|
|
4055
|
-
this.loggerService.log("
|
|
4074
|
+
this.loggerService.log("exchangeCoreService getCandles", {
|
|
4056
4075
|
symbol,
|
|
4057
4076
|
interval,
|
|
4058
4077
|
limit,
|
|
4059
4078
|
when,
|
|
4060
4079
|
backtest,
|
|
4061
4080
|
});
|
|
4081
|
+
if (!MethodContextService.hasContext()) {
|
|
4082
|
+
throw new Error("exchangeCoreService getCandles requires a method context");
|
|
4083
|
+
}
|
|
4062
4084
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4063
4085
|
return await ExecutionContextService.runInContext(async () => {
|
|
4064
4086
|
return await this.exchangeConnectionService.getCandles(symbol, interval, limit);
|
|
@@ -4079,13 +4101,16 @@ class ExchangeGlobalService {
|
|
|
4079
4101
|
* @returns Promise resolving to array of future candles
|
|
4080
4102
|
*/
|
|
4081
4103
|
this.getNextCandles = async (symbol, interval, limit, when, backtest) => {
|
|
4082
|
-
this.loggerService.log("
|
|
4104
|
+
this.loggerService.log("exchangeCoreService getNextCandles", {
|
|
4083
4105
|
symbol,
|
|
4084
4106
|
interval,
|
|
4085
4107
|
limit,
|
|
4086
4108
|
when,
|
|
4087
4109
|
backtest,
|
|
4088
4110
|
});
|
|
4111
|
+
if (!MethodContextService.hasContext()) {
|
|
4112
|
+
throw new Error("exchangeCoreService getNextCandles requires a method context");
|
|
4113
|
+
}
|
|
4089
4114
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4090
4115
|
return await ExecutionContextService.runInContext(async () => {
|
|
4091
4116
|
return await this.exchangeConnectionService.getNextCandles(symbol, interval, limit);
|
|
@@ -4104,11 +4129,14 @@ class ExchangeGlobalService {
|
|
|
4104
4129
|
* @returns Promise resolving to VWAP price
|
|
4105
4130
|
*/
|
|
4106
4131
|
this.getAveragePrice = async (symbol, when, backtest) => {
|
|
4107
|
-
this.loggerService.log("
|
|
4132
|
+
this.loggerService.log("exchangeCoreService getAveragePrice", {
|
|
4108
4133
|
symbol,
|
|
4109
4134
|
when,
|
|
4110
4135
|
backtest,
|
|
4111
4136
|
});
|
|
4137
|
+
if (!MethodContextService.hasContext()) {
|
|
4138
|
+
throw new Error("exchangeCoreService getAveragePrice requires a method context");
|
|
4139
|
+
}
|
|
4112
4140
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4113
4141
|
return await ExecutionContextService.runInContext(async () => {
|
|
4114
4142
|
return await this.exchangeConnectionService.getAveragePrice(symbol);
|
|
@@ -4128,12 +4156,15 @@ class ExchangeGlobalService {
|
|
|
4128
4156
|
* @returns Promise resolving to formatted price string
|
|
4129
4157
|
*/
|
|
4130
4158
|
this.formatPrice = async (symbol, price, when, backtest) => {
|
|
4131
|
-
this.loggerService.log("
|
|
4159
|
+
this.loggerService.log("exchangeCoreService formatPrice", {
|
|
4132
4160
|
symbol,
|
|
4133
4161
|
price,
|
|
4134
4162
|
when,
|
|
4135
4163
|
backtest,
|
|
4136
4164
|
});
|
|
4165
|
+
if (!MethodContextService.hasContext()) {
|
|
4166
|
+
throw new Error("exchangeCoreService formatPrice requires a method context");
|
|
4167
|
+
}
|
|
4137
4168
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4138
4169
|
return await ExecutionContextService.runInContext(async () => {
|
|
4139
4170
|
return await this.exchangeConnectionService.formatPrice(symbol, price);
|
|
@@ -4153,12 +4184,15 @@ class ExchangeGlobalService {
|
|
|
4153
4184
|
* @returns Promise resolving to formatted quantity string
|
|
4154
4185
|
*/
|
|
4155
4186
|
this.formatQuantity = async (symbol, quantity, when, backtest) => {
|
|
4156
|
-
this.loggerService.log("
|
|
4187
|
+
this.loggerService.log("exchangeCoreService formatQuantity", {
|
|
4157
4188
|
symbol,
|
|
4158
4189
|
quantity,
|
|
4159
4190
|
when,
|
|
4160
4191
|
backtest,
|
|
4161
4192
|
});
|
|
4193
|
+
if (!MethodContextService.hasContext()) {
|
|
4194
|
+
throw new Error("exchangeCoreService formatQuantity requires a method context");
|
|
4195
|
+
}
|
|
4162
4196
|
await this.validate(this.methodContextService.context.exchangeName);
|
|
4163
4197
|
return await ExecutionContextService.runInContext(async () => {
|
|
4164
4198
|
return await this.exchangeConnectionService.formatQuantity(symbol, quantity);
|
|
@@ -4171,7 +4205,7 @@ class ExchangeGlobalService {
|
|
|
4171
4205
|
}
|
|
4172
4206
|
}
|
|
4173
4207
|
|
|
4174
|
-
const METHOD_NAME_VALIDATE = "
|
|
4208
|
+
const METHOD_NAME_VALIDATE = "strategyCoreService validate";
|
|
4175
4209
|
/**
|
|
4176
4210
|
* Global service for strategy operations with execution context injection.
|
|
4177
4211
|
*
|
|
@@ -4180,7 +4214,7 @@ const METHOD_NAME_VALIDATE = "strategyGlobalService validate";
|
|
|
4180
4214
|
*
|
|
4181
4215
|
* Used internally by BacktestLogicPrivateService and LiveLogicPrivateService.
|
|
4182
4216
|
*/
|
|
4183
|
-
class
|
|
4217
|
+
class StrategyCoreService {
|
|
4184
4218
|
constructor() {
|
|
4185
4219
|
this.loggerService = inject(TYPES.loggerService);
|
|
4186
4220
|
this.strategyConnectionService = inject(TYPES.strategyConnectionService);
|
|
@@ -4218,10 +4252,13 @@ class StrategyGlobalService {
|
|
|
4218
4252
|
* @returns Promise resolving to pending signal or null
|
|
4219
4253
|
*/
|
|
4220
4254
|
this.getPendingSignal = async (symbol, strategyName) => {
|
|
4221
|
-
this.loggerService.log("
|
|
4255
|
+
this.loggerService.log("strategyCoreService getPendingSignal", {
|
|
4222
4256
|
symbol,
|
|
4223
4257
|
strategyName,
|
|
4224
4258
|
});
|
|
4259
|
+
if (!MethodContextService.hasContext()) {
|
|
4260
|
+
throw new Error("strategyCoreService getPendingSignal requires a method context");
|
|
4261
|
+
}
|
|
4225
4262
|
await this.validate(symbol, strategyName);
|
|
4226
4263
|
return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
|
|
4227
4264
|
};
|
|
@@ -4236,10 +4273,13 @@ class StrategyGlobalService {
|
|
|
4236
4273
|
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
4237
4274
|
*/
|
|
4238
4275
|
this.getStopped = async (symbol, strategyName) => {
|
|
4239
|
-
this.loggerService.log("
|
|
4276
|
+
this.loggerService.log("strategyCoreService getStopped", {
|
|
4240
4277
|
symbol,
|
|
4241
4278
|
strategyName,
|
|
4242
4279
|
});
|
|
4280
|
+
if (!MethodContextService.hasContext()) {
|
|
4281
|
+
throw new Error("strategyCoreService getStopped requires a method context");
|
|
4282
|
+
}
|
|
4243
4283
|
await this.validate(symbol, strategyName);
|
|
4244
4284
|
return await this.strategyConnectionService.getStopped(symbol, strategyName);
|
|
4245
4285
|
};
|
|
@@ -4255,11 +4295,14 @@ class StrategyGlobalService {
|
|
|
4255
4295
|
* @returns Discriminated union of tick result (idle, opened, active, closed)
|
|
4256
4296
|
*/
|
|
4257
4297
|
this.tick = async (symbol, when, backtest) => {
|
|
4258
|
-
this.loggerService.log("
|
|
4298
|
+
this.loggerService.log("strategyCoreService tick", {
|
|
4259
4299
|
symbol,
|
|
4260
4300
|
when,
|
|
4261
4301
|
backtest,
|
|
4262
4302
|
});
|
|
4303
|
+
if (!MethodContextService.hasContext()) {
|
|
4304
|
+
throw new Error("strategyCoreService tick requires a method context");
|
|
4305
|
+
}
|
|
4263
4306
|
const strategyName = this.methodContextService.context.strategyName;
|
|
4264
4307
|
await this.validate(symbol, strategyName);
|
|
4265
4308
|
return await ExecutionContextService.runInContext(async () => {
|
|
@@ -4283,12 +4326,15 @@ class StrategyGlobalService {
|
|
|
4283
4326
|
* @returns Closed signal result with PNL
|
|
4284
4327
|
*/
|
|
4285
4328
|
this.backtest = async (symbol, candles, when, backtest) => {
|
|
4286
|
-
this.loggerService.log("
|
|
4329
|
+
this.loggerService.log("strategyCoreService backtest", {
|
|
4287
4330
|
symbol,
|
|
4288
4331
|
candleCount: candles.length,
|
|
4289
4332
|
when,
|
|
4290
4333
|
backtest,
|
|
4291
4334
|
});
|
|
4335
|
+
if (!MethodContextService.hasContext()) {
|
|
4336
|
+
throw new Error("strategyCoreService backtest requires a method context");
|
|
4337
|
+
}
|
|
4292
4338
|
const strategyName = this.methodContextService.context.strategyName;
|
|
4293
4339
|
await this.validate(symbol, strategyName);
|
|
4294
4340
|
return await ExecutionContextService.runInContext(async () => {
|
|
@@ -4310,7 +4356,7 @@ class StrategyGlobalService {
|
|
|
4310
4356
|
* @returns Promise that resolves when stop flag is set
|
|
4311
4357
|
*/
|
|
4312
4358
|
this.stop = async (ctx, backtest) => {
|
|
4313
|
-
this.loggerService.log("
|
|
4359
|
+
this.loggerService.log("strategyCoreService stop", {
|
|
4314
4360
|
ctx,
|
|
4315
4361
|
backtest,
|
|
4316
4362
|
});
|
|
@@ -4326,7 +4372,7 @@ class StrategyGlobalService {
|
|
|
4326
4372
|
* @param ctx - Optional context with symbol and strategyName (clears all if not provided)
|
|
4327
4373
|
*/
|
|
4328
4374
|
this.clear = async (ctx) => {
|
|
4329
|
-
this.loggerService.log("
|
|
4375
|
+
this.loggerService.log("strategyCoreService clear", {
|
|
4330
4376
|
ctx,
|
|
4331
4377
|
});
|
|
4332
4378
|
if (ctx) {
|
|
@@ -4337,14 +4383,14 @@ class StrategyGlobalService {
|
|
|
4337
4383
|
}
|
|
4338
4384
|
}
|
|
4339
4385
|
|
|
4340
|
-
const METHOD_NAME_GET_TIMEFRAME = "
|
|
4386
|
+
const METHOD_NAME_GET_TIMEFRAME = "frameCoreService getTimeframe";
|
|
4341
4387
|
/**
|
|
4342
4388
|
* Global service for frame operations.
|
|
4343
4389
|
*
|
|
4344
4390
|
* Wraps FrameConnectionService for timeframe generation.
|
|
4345
4391
|
* Used internally by BacktestLogicPrivateService.
|
|
4346
4392
|
*/
|
|
4347
|
-
class
|
|
4393
|
+
class FrameCoreService {
|
|
4348
4394
|
constructor() {
|
|
4349
4395
|
this.loggerService = inject(TYPES.loggerService);
|
|
4350
4396
|
this.frameConnectionService = inject(TYPES.frameConnectionService);
|
|
@@ -4360,6 +4406,9 @@ class FrameGlobalService {
|
|
|
4360
4406
|
frameName,
|
|
4361
4407
|
symbol,
|
|
4362
4408
|
});
|
|
4409
|
+
if (!MethodContextService.hasContext()) {
|
|
4410
|
+
throw new Error("frameCoreService getTimeframe requires a method context");
|
|
4411
|
+
}
|
|
4363
4412
|
this.frameValidationService.validate(frameName, METHOD_NAME_GET_TIMEFRAME);
|
|
4364
4413
|
return await this.frameConnectionService.getTimeframe(symbol, frameName);
|
|
4365
4414
|
};
|
|
@@ -4965,9 +5014,9 @@ class WalkerSchemaService {
|
|
|
4965
5014
|
class BacktestLogicPrivateService {
|
|
4966
5015
|
constructor() {
|
|
4967
5016
|
this.loggerService = inject(TYPES.loggerService);
|
|
4968
|
-
this.
|
|
4969
|
-
this.
|
|
4970
|
-
this.
|
|
5017
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
5018
|
+
this.exchangeCoreService = inject(TYPES.exchangeCoreService);
|
|
5019
|
+
this.frameCoreService = inject(TYPES.frameCoreService);
|
|
4971
5020
|
this.methodContextService = inject(TYPES.methodContextService);
|
|
4972
5021
|
}
|
|
4973
5022
|
/**
|
|
@@ -4989,7 +5038,7 @@ class BacktestLogicPrivateService {
|
|
|
4989
5038
|
symbol,
|
|
4990
5039
|
});
|
|
4991
5040
|
const backtestStartTime = performance.now();
|
|
4992
|
-
const timeframes = await this.
|
|
5041
|
+
const timeframes = await this.frameCoreService.getTimeframe(symbol, this.methodContextService.context.frameName);
|
|
4993
5042
|
const totalFrames = timeframes.length;
|
|
4994
5043
|
let i = 0;
|
|
4995
5044
|
let previousEventTimestamp = null;
|
|
@@ -5008,7 +5057,7 @@ class BacktestLogicPrivateService {
|
|
|
5008
5057
|
});
|
|
5009
5058
|
}
|
|
5010
5059
|
// Check if strategy should stop before processing next frame
|
|
5011
|
-
if (await this.
|
|
5060
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5012
5061
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
|
|
5013
5062
|
symbol,
|
|
5014
5063
|
when: when.toISOString(),
|
|
@@ -5019,7 +5068,7 @@ class BacktestLogicPrivateService {
|
|
|
5019
5068
|
}
|
|
5020
5069
|
let result;
|
|
5021
5070
|
try {
|
|
5022
|
-
result = await this.
|
|
5071
|
+
result = await this.strategyCoreService.tick(symbol, when, true);
|
|
5023
5072
|
}
|
|
5024
5073
|
catch (error) {
|
|
5025
5074
|
console.warn(`backtestLogicPrivateService tick failed, skipping timeframe when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5033,7 +5082,7 @@ class BacktestLogicPrivateService {
|
|
|
5033
5082
|
continue;
|
|
5034
5083
|
}
|
|
5035
5084
|
// Check if strategy should stop when idle (no active signal)
|
|
5036
|
-
if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.
|
|
5085
|
+
if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
5037
5086
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
|
|
5038
5087
|
symbol,
|
|
5039
5088
|
when: when.toISOString(),
|
|
@@ -5063,7 +5112,7 @@ class BacktestLogicPrivateService {
|
|
|
5063
5112
|
const candlesNeeded = bufferMinutes + GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES + signal.minuteEstimatedTime + 1;
|
|
5064
5113
|
let candles;
|
|
5065
5114
|
try {
|
|
5066
|
-
candles = await this.
|
|
5115
|
+
candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", candlesNeeded, bufferStartTime, true);
|
|
5067
5116
|
}
|
|
5068
5117
|
catch (error) {
|
|
5069
5118
|
console.warn(`backtestLogicPrivateService getNextCandles failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5092,7 +5141,7 @@ class BacktestLogicPrivateService {
|
|
|
5092
5141
|
// и если активируется - продолжит с TP/SL мониторингом
|
|
5093
5142
|
let backtestResult;
|
|
5094
5143
|
try {
|
|
5095
|
-
backtestResult = await this.
|
|
5144
|
+
backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
|
|
5096
5145
|
}
|
|
5097
5146
|
catch (error) {
|
|
5098
5147
|
console.warn(`backtestLogicPrivateService backtest failed for scheduled signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5135,7 +5184,7 @@ class BacktestLogicPrivateService {
|
|
|
5135
5184
|
}
|
|
5136
5185
|
yield backtestResult;
|
|
5137
5186
|
// Check if strategy should stop after signal is closed
|
|
5138
|
-
if (await this.
|
|
5187
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5139
5188
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
|
|
5140
5189
|
symbol,
|
|
5141
5190
|
signalId: backtestResult.signal.id,
|
|
@@ -5162,7 +5211,7 @@ class BacktestLogicPrivateService {
|
|
|
5162
5211
|
const totalCandles = signal.minuteEstimatedTime + bufferMinutes;
|
|
5163
5212
|
let candles;
|
|
5164
5213
|
try {
|
|
5165
|
-
candles = await this.
|
|
5214
|
+
candles = await this.exchangeCoreService.getNextCandles(symbol, "1m", totalCandles, bufferStartTime, true);
|
|
5166
5215
|
}
|
|
5167
5216
|
catch (error) {
|
|
5168
5217
|
console.warn(`backtestLogicPrivateService getNextCandles failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5189,7 +5238,7 @@ class BacktestLogicPrivateService {
|
|
|
5189
5238
|
// Вызываем backtest - всегда возвращает closed
|
|
5190
5239
|
let backtestResult;
|
|
5191
5240
|
try {
|
|
5192
|
-
backtestResult = await this.
|
|
5241
|
+
backtestResult = await this.strategyCoreService.backtest(symbol, candles, when, true);
|
|
5193
5242
|
}
|
|
5194
5243
|
catch (error) {
|
|
5195
5244
|
console.warn(`backtestLogicPrivateService backtest failed for opened signal when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5228,7 +5277,7 @@ class BacktestLogicPrivateService {
|
|
|
5228
5277
|
}
|
|
5229
5278
|
yield backtestResult;
|
|
5230
5279
|
// Check if strategy should stop after signal is closed
|
|
5231
|
-
if (await this.
|
|
5280
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5232
5281
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
|
|
5233
5282
|
symbol,
|
|
5234
5283
|
signalId: backtestResult.signal.id,
|
|
@@ -5302,7 +5351,7 @@ const TICK_TTL = 1 * 60 * 1000 + 1;
|
|
|
5302
5351
|
class LiveLogicPrivateService {
|
|
5303
5352
|
constructor() {
|
|
5304
5353
|
this.loggerService = inject(TYPES.loggerService);
|
|
5305
|
-
this.
|
|
5354
|
+
this.strategyCoreService = inject(TYPES.strategyCoreService);
|
|
5306
5355
|
this.methodContextService = inject(TYPES.methodContextService);
|
|
5307
5356
|
}
|
|
5308
5357
|
/**
|
|
@@ -5337,7 +5386,7 @@ class LiveLogicPrivateService {
|
|
|
5337
5386
|
const when = new Date();
|
|
5338
5387
|
let result;
|
|
5339
5388
|
try {
|
|
5340
|
-
result = await this.
|
|
5389
|
+
result = await this.strategyCoreService.tick(symbol, when, false);
|
|
5341
5390
|
}
|
|
5342
5391
|
catch (error) {
|
|
5343
5392
|
console.warn(`backtestLogicPrivateService tick failed when=${when.toISOString()} symbol=${symbol} strategyName=${this.methodContextService.context.strategyName} exchangeName=${this.methodContextService.context.exchangeName}`);
|
|
@@ -5371,7 +5420,7 @@ class LiveLogicPrivateService {
|
|
|
5371
5420
|
previousEventTimestamp = currentTimestamp;
|
|
5372
5421
|
// Check if strategy should stop when idle (no active signal)
|
|
5373
5422
|
if (result.action === "idle") {
|
|
5374
|
-
if (await functoolsKit.and(Promise.resolve(true), this.
|
|
5423
|
+
if (await functoolsKit.and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
5375
5424
|
this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
|
|
5376
5425
|
symbol,
|
|
5377
5426
|
when: when.toISOString(),
|
|
@@ -5393,7 +5442,7 @@ class LiveLogicPrivateService {
|
|
|
5393
5442
|
yield result;
|
|
5394
5443
|
// Check if strategy should stop after signal is closed
|
|
5395
5444
|
if (result.action === "closed") {
|
|
5396
|
-
if (await this.
|
|
5445
|
+
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5397
5446
|
this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
|
|
5398
5447
|
symbol,
|
|
5399
5448
|
signalId: result.signal.id,
|
|
@@ -8684,8 +8733,27 @@ class HeatMarkdownService {
|
|
|
8684
8733
|
}
|
|
8685
8734
|
|
|
8686
8735
|
/**
|
|
8687
|
-
*
|
|
8688
|
-
*
|
|
8736
|
+
* Service for managing and validating exchange configurations.
|
|
8737
|
+
*
|
|
8738
|
+
* Maintains a registry of all configured exchanges and validates
|
|
8739
|
+
* their existence before operations. Uses memoization for performance.
|
|
8740
|
+
*
|
|
8741
|
+
* Key features:
|
|
8742
|
+
* - Registry management: addExchange() to register new exchanges
|
|
8743
|
+
* - Validation: validate() ensures exchange exists before use
|
|
8744
|
+
* - Memoization: validation results are cached for performance
|
|
8745
|
+
* - Listing: list() returns all registered exchanges
|
|
8746
|
+
*
|
|
8747
|
+
* @throws {Error} If duplicate exchange name is added
|
|
8748
|
+
* @throws {Error} If unknown exchange is referenced
|
|
8749
|
+
*
|
|
8750
|
+
* @example
|
|
8751
|
+
* ```typescript
|
|
8752
|
+
* const exchangeValidation = new ExchangeValidationService();
|
|
8753
|
+
* exchangeValidation.addExchange("binance", binanceSchema);
|
|
8754
|
+
* exchangeValidation.validate("binance", "backtest"); // OK
|
|
8755
|
+
* exchangeValidation.validate("unknown", "live"); // Throws error
|
|
8756
|
+
* ```
|
|
8689
8757
|
*/
|
|
8690
8758
|
class ExchangeValidationService {
|
|
8691
8759
|
constructor() {
|
|
@@ -8745,8 +8813,29 @@ class ExchangeValidationService {
|
|
|
8745
8813
|
}
|
|
8746
8814
|
|
|
8747
8815
|
/**
|
|
8748
|
-
*
|
|
8749
|
-
*
|
|
8816
|
+
* Service for managing and validating trading strategy configurations.
|
|
8817
|
+
*
|
|
8818
|
+
* Maintains a registry of all configured strategies, validates their existence
|
|
8819
|
+
* before operations, and ensures associated risk profiles are valid.
|
|
8820
|
+
* Uses memoization for performance.
|
|
8821
|
+
*
|
|
8822
|
+
* Key features:
|
|
8823
|
+
* - Registry management: addStrategy() to register new strategies
|
|
8824
|
+
* - Dual validation: validates both strategy existence and risk profile (if configured)
|
|
8825
|
+
* - Memoization: validation results are cached for performance
|
|
8826
|
+
* - Listing: list() returns all registered strategies
|
|
8827
|
+
*
|
|
8828
|
+
* @throws {Error} If duplicate strategy name is added
|
|
8829
|
+
* @throws {Error} If unknown strategy is referenced
|
|
8830
|
+
* @throws {Error} If strategy's risk profile doesn't exist
|
|
8831
|
+
*
|
|
8832
|
+
* @example
|
|
8833
|
+
* ```typescript
|
|
8834
|
+
* const strategyValidation = new StrategyValidationService();
|
|
8835
|
+
* strategyValidation.addStrategy("momentum-btc", { ...schema, riskName: "conservative" });
|
|
8836
|
+
* strategyValidation.validate("momentum-btc", "backtest"); // Validates strategy + risk
|
|
8837
|
+
* strategyValidation.validate("unknown", "live"); // Throws error
|
|
8838
|
+
* ```
|
|
8750
8839
|
*/
|
|
8751
8840
|
class StrategyValidationService {
|
|
8752
8841
|
constructor() {
|
|
@@ -8817,8 +8906,27 @@ class StrategyValidationService {
|
|
|
8817
8906
|
}
|
|
8818
8907
|
|
|
8819
8908
|
/**
|
|
8820
|
-
*
|
|
8821
|
-
*
|
|
8909
|
+
* Service for managing and validating frame (timeframe) configurations.
|
|
8910
|
+
*
|
|
8911
|
+
* Maintains a registry of all configured frames and validates
|
|
8912
|
+
* their existence before operations. Uses memoization for performance.
|
|
8913
|
+
*
|
|
8914
|
+
* Key features:
|
|
8915
|
+
* - Registry management: addFrame() to register new timeframes
|
|
8916
|
+
* - Validation: validate() ensures frame exists before use
|
|
8917
|
+
* - Memoization: validation results are cached for performance
|
|
8918
|
+
* - Listing: list() returns all registered frames
|
|
8919
|
+
*
|
|
8920
|
+
* @throws {Error} If duplicate frame name is added
|
|
8921
|
+
* @throws {Error} If unknown frame is referenced
|
|
8922
|
+
*
|
|
8923
|
+
* @example
|
|
8924
|
+
* ```typescript
|
|
8925
|
+
* const frameValidation = new FrameValidationService();
|
|
8926
|
+
* frameValidation.addFrame("2024-Q1", frameSchema);
|
|
8927
|
+
* frameValidation.validate("2024-Q1", "backtest"); // OK
|
|
8928
|
+
* frameValidation.validate("unknown", "live"); // Throws error
|
|
8929
|
+
* ```
|
|
8822
8930
|
*/
|
|
8823
8931
|
class FrameValidationService {
|
|
8824
8932
|
constructor() {
|
|
@@ -8878,8 +8986,29 @@ class FrameValidationService {
|
|
|
8878
8986
|
}
|
|
8879
8987
|
|
|
8880
8988
|
/**
|
|
8881
|
-
*
|
|
8882
|
-
*
|
|
8989
|
+
* Service for managing and validating walker (parameter sweep) configurations.
|
|
8990
|
+
*
|
|
8991
|
+
* Maintains a registry of all configured walkers and validates
|
|
8992
|
+
* their existence before operations. Uses memoization for performance.
|
|
8993
|
+
*
|
|
8994
|
+
* Walkers define parameter ranges for optimization and hyperparameter tuning.
|
|
8995
|
+
*
|
|
8996
|
+
* Key features:
|
|
8997
|
+
* - Registry management: addWalker() to register new walker configurations
|
|
8998
|
+
* - Validation: validate() ensures walker exists before use
|
|
8999
|
+
* - Memoization: validation results are cached for performance
|
|
9000
|
+
* - Listing: list() returns all registered walkers
|
|
9001
|
+
*
|
|
9002
|
+
* @throws {Error} If duplicate walker name is added
|
|
9003
|
+
* @throws {Error} If unknown walker is referenced
|
|
9004
|
+
*
|
|
9005
|
+
* @example
|
|
9006
|
+
* ```typescript
|
|
9007
|
+
* const walkerValidation = new WalkerValidationService();
|
|
9008
|
+
* walkerValidation.addWalker("rsi-sweep", walkerSchema);
|
|
9009
|
+
* walkerValidation.validate("rsi-sweep", "optimizer"); // OK
|
|
9010
|
+
* walkerValidation.validate("unknown", "optimizer"); // Throws error
|
|
9011
|
+
* ```
|
|
8883
9012
|
*/
|
|
8884
9013
|
class WalkerValidationService {
|
|
8885
9014
|
constructor() {
|
|
@@ -8939,8 +9068,27 @@ class WalkerValidationService {
|
|
|
8939
9068
|
}
|
|
8940
9069
|
|
|
8941
9070
|
/**
|
|
8942
|
-
*
|
|
8943
|
-
*
|
|
9071
|
+
* Service for managing and validating position sizing configurations.
|
|
9072
|
+
*
|
|
9073
|
+
* Maintains a registry of all configured sizing strategies and validates
|
|
9074
|
+
* their existence before operations. Uses memoization for performance.
|
|
9075
|
+
*
|
|
9076
|
+
* Key features:
|
|
9077
|
+
* - Registry management: addSizing() to register new sizing strategies
|
|
9078
|
+
* - Validation: validate() ensures sizing strategy exists before use
|
|
9079
|
+
* - Memoization: validation results are cached for performance
|
|
9080
|
+
* - Listing: list() returns all registered sizing strategies
|
|
9081
|
+
*
|
|
9082
|
+
* @throws {Error} If duplicate sizing name is added
|
|
9083
|
+
* @throws {Error} If unknown sizing strategy is referenced
|
|
9084
|
+
*
|
|
9085
|
+
* @example
|
|
9086
|
+
* ```typescript
|
|
9087
|
+
* const sizingValidation = new SizingValidationService();
|
|
9088
|
+
* sizingValidation.addSizing("fixed-1000", fixedSizingSchema);
|
|
9089
|
+
* sizingValidation.validate("fixed-1000", "strategy-1"); // OK
|
|
9090
|
+
* sizingValidation.validate("unknown", "strategy-2"); // Throws error
|
|
9091
|
+
* ```
|
|
8944
9092
|
*/
|
|
8945
9093
|
class SizingValidationService {
|
|
8946
9094
|
constructor() {
|
|
@@ -9005,8 +9153,27 @@ class SizingValidationService {
|
|
|
9005
9153
|
}
|
|
9006
9154
|
|
|
9007
9155
|
/**
|
|
9008
|
-
*
|
|
9009
|
-
*
|
|
9156
|
+
* Service for managing and validating risk management configurations.
|
|
9157
|
+
*
|
|
9158
|
+
* Maintains a registry of all configured risk profiles and validates
|
|
9159
|
+
* their existence before operations. Uses memoization for performance.
|
|
9160
|
+
*
|
|
9161
|
+
* Key features:
|
|
9162
|
+
* - Registry management: addRisk() to register new risk profiles
|
|
9163
|
+
* - Validation: validate() ensures risk profile exists before use
|
|
9164
|
+
* - Memoization: validation results are cached by riskName:source for performance
|
|
9165
|
+
* - Listing: list() returns all registered risk profiles
|
|
9166
|
+
*
|
|
9167
|
+
* @throws {Error} If duplicate risk name is added
|
|
9168
|
+
* @throws {Error} If unknown risk profile is referenced
|
|
9169
|
+
*
|
|
9170
|
+
* @example
|
|
9171
|
+
* ```typescript
|
|
9172
|
+
* const riskValidation = new RiskValidationService();
|
|
9173
|
+
* riskValidation.addRisk("conservative", conservativeSchema);
|
|
9174
|
+
* riskValidation.validate("conservative", "strategy-1"); // OK
|
|
9175
|
+
* riskValidation.validate("unknown", "strategy-2"); // Throws error
|
|
9176
|
+
* ```
|
|
9010
9177
|
*/
|
|
9011
9178
|
class RiskValidationService {
|
|
9012
9179
|
constructor() {
|
|
@@ -11558,6 +11725,133 @@ class OutlineMarkdownService {
|
|
|
11558
11725
|
}
|
|
11559
11726
|
}
|
|
11560
11727
|
|
|
11728
|
+
/**
|
|
11729
|
+
* Service for validating GLOBAL_CONFIG parameters to ensure mathematical correctness
|
|
11730
|
+
* and prevent unprofitable trading configurations.
|
|
11731
|
+
*
|
|
11732
|
+
* Performs comprehensive validation on:
|
|
11733
|
+
* - **Percentage parameters**: Slippage, fees, and profit margins must be non-negative
|
|
11734
|
+
* - **Economic viability**: Ensures CC_MIN_TAKEPROFIT_DISTANCE_PERCENT covers all trading costs
|
|
11735
|
+
* (slippage + fees) to guarantee profitable trades when TakeProfit is hit
|
|
11736
|
+
* - **Range constraints**: Validates MIN < MAX relationships (e.g., StopLoss distances)
|
|
11737
|
+
* - **Time-based parameters**: Ensures positive integer values for timeouts and lifetimes
|
|
11738
|
+
* - **Candle parameters**: Validates retry counts, delays, and anomaly detection thresholds
|
|
11739
|
+
*
|
|
11740
|
+
* @throws {Error} If any validation fails, throws with detailed breakdown of all errors
|
|
11741
|
+
*
|
|
11742
|
+
* @example
|
|
11743
|
+
* ```typescript
|
|
11744
|
+
* const validator = new ConfigValidationService();
|
|
11745
|
+
* validator.validate(); // Throws if config is invalid
|
|
11746
|
+
* ```
|
|
11747
|
+
*
|
|
11748
|
+
* @example Validation failure output:
|
|
11749
|
+
* ```
|
|
11750
|
+
* GLOBAL_CONFIG validation failed:
|
|
11751
|
+
* 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (0.3%) is too low to cover trading costs.
|
|
11752
|
+
* Required minimum: 0.40%
|
|
11753
|
+
* Breakdown:
|
|
11754
|
+
* - Slippage effect: 0.20% (0.1% × 2 transactions)
|
|
11755
|
+
* - Fees: 0.20% (0.1% × 2 transactions)
|
|
11756
|
+
* All TakeProfit signals will be unprofitable with current settings!
|
|
11757
|
+
* ```
|
|
11758
|
+
*/
|
|
11759
|
+
class ConfigValidationService {
|
|
11760
|
+
constructor() {
|
|
11761
|
+
/**
|
|
11762
|
+
* @private
|
|
11763
|
+
* @readonly
|
|
11764
|
+
* Injected logger service instance
|
|
11765
|
+
*/
|
|
11766
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
11767
|
+
/**
|
|
11768
|
+
* Validates GLOBAL_CONFIG parameters for mathematical correctness.
|
|
11769
|
+
*
|
|
11770
|
+
* Checks:
|
|
11771
|
+
* 1. CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must cover slippage + fees
|
|
11772
|
+
* 2. All percentage values must be positive
|
|
11773
|
+
* 3. Time/count values must be positive integers
|
|
11774
|
+
*
|
|
11775
|
+
* @throws Error if configuration is invalid
|
|
11776
|
+
*/
|
|
11777
|
+
this.validate = () => {
|
|
11778
|
+
this.loggerService.log("configValidationService validate");
|
|
11779
|
+
const errors = [];
|
|
11780
|
+
// Validate slippage and fee percentages
|
|
11781
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE) || GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE < 0) {
|
|
11782
|
+
errors.push(`CC_PERCENT_SLIPPAGE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}`);
|
|
11783
|
+
}
|
|
11784
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_PERCENT_FEE) || GLOBAL_CONFIG.CC_PERCENT_FEE < 0) {
|
|
11785
|
+
errors.push(`CC_PERCENT_FEE must be a non-negative number, got ${GLOBAL_CONFIG.CC_PERCENT_FEE}`);
|
|
11786
|
+
}
|
|
11787
|
+
// Calculate minimum required TP distance to cover costs
|
|
11788
|
+
const slippageEffect = GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE * 2; // Applied twice (entry + exit)
|
|
11789
|
+
const feesTotal = GLOBAL_CONFIG.CC_PERCENT_FEE * 2; // Applied twice (entry + exit)
|
|
11790
|
+
const minRequiredTpDistance = slippageEffect + feesTotal;
|
|
11791
|
+
// Validate CC_MIN_TAKEPROFIT_DISTANCE_PERCENT
|
|
11792
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT <= 0) {
|
|
11793
|
+
errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}`);
|
|
11794
|
+
}
|
|
11795
|
+
else if (GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT < minRequiredTpDistance) {
|
|
11796
|
+
errors.push(`CC_MIN_TAKEPROFIT_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_TAKEPROFIT_DISTANCE_PERCENT}%) is too low to cover trading costs.\n` +
|
|
11797
|
+
` Required minimum: ${minRequiredTpDistance.toFixed(2)}%\n` +
|
|
11798
|
+
` Breakdown:\n` +
|
|
11799
|
+
` - Slippage effect: ${slippageEffect.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE}% × 2 transactions)\n` +
|
|
11800
|
+
` - Fees: ${feesTotal.toFixed(2)}% (${GLOBAL_CONFIG.CC_PERCENT_FEE}% × 2 transactions)\n` +
|
|
11801
|
+
` All TakeProfit signals will be unprofitable with current settings!`);
|
|
11802
|
+
}
|
|
11803
|
+
// Validate CC_MIN_STOPLOSS_DISTANCE_PERCENT
|
|
11804
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT <= 0) {
|
|
11805
|
+
errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}`);
|
|
11806
|
+
}
|
|
11807
|
+
// Validate CC_MAX_STOPLOSS_DISTANCE_PERCENT
|
|
11808
|
+
if (!Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) || GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT <= 0) {
|
|
11809
|
+
errors.push(`CC_MAX_STOPLOSS_DISTANCE_PERCENT must be a positive number, got ${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}`);
|
|
11810
|
+
}
|
|
11811
|
+
// Validate that MIN < MAX for StopLoss
|
|
11812
|
+
if (Number.isFinite(GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT) &&
|
|
11813
|
+
Number.isFinite(GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) &&
|
|
11814
|
+
GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT >= GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT) {
|
|
11815
|
+
errors.push(`CC_MIN_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MIN_STOPLOSS_DISTANCE_PERCENT}%) must be less than ` +
|
|
11816
|
+
`CC_MAX_STOPLOSS_DISTANCE_PERCENT (${GLOBAL_CONFIG.CC_MAX_STOPLOSS_DISTANCE_PERCENT}%)`);
|
|
11817
|
+
}
|
|
11818
|
+
// Validate time-based parameters
|
|
11819
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES) || GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES <= 0) {
|
|
11820
|
+
errors.push(`CC_SCHEDULE_AWAIT_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_SCHEDULE_AWAIT_MINUTES}`);
|
|
11821
|
+
}
|
|
11822
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES) || GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES <= 0) {
|
|
11823
|
+
errors.push(`CC_MAX_SIGNAL_LIFETIME_MINUTES must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES}`);
|
|
11824
|
+
}
|
|
11825
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS) || GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS <= 0) {
|
|
11826
|
+
errors.push(`CC_MAX_SIGNAL_GENERATION_SECONDS must be a positive integer, got ${GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS}`);
|
|
11827
|
+
}
|
|
11828
|
+
// Validate candle-based parameters
|
|
11829
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT) || GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT <= 0) {
|
|
11830
|
+
errors.push(`CC_AVG_PRICE_CANDLES_COUNT must be a positive integer, got ${GLOBAL_CONFIG.CC_AVG_PRICE_CANDLES_COUNT}`);
|
|
11831
|
+
}
|
|
11832
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT < 0) {
|
|
11833
|
+
errors.push(`CC_GET_CANDLES_RETRY_COUNT must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_COUNT}`);
|
|
11834
|
+
}
|
|
11835
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS) || GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS < 0) {
|
|
11836
|
+
errors.push(`CC_GET_CANDLES_RETRY_DELAY_MS must be a non-negative integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS}`);
|
|
11837
|
+
}
|
|
11838
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR) || GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR <= 0) {
|
|
11839
|
+
errors.push(`CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_PRICE_ANOMALY_THRESHOLD_FACTOR}`);
|
|
11840
|
+
}
|
|
11841
|
+
if (!Number.isInteger(GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN) || GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN <= 0) {
|
|
11842
|
+
errors.push(`CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN must be a positive integer, got ${GLOBAL_CONFIG.CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN}`);
|
|
11843
|
+
}
|
|
11844
|
+
// Throw aggregated errors if any
|
|
11845
|
+
if (errors.length > 0) {
|
|
11846
|
+
const errorMessage = `GLOBAL_CONFIG validation failed:\n${errors.map((e, i) => ` ${i + 1}. ${e}`).join('\n')}`;
|
|
11847
|
+
this.loggerService.warn(errorMessage);
|
|
11848
|
+
throw new Error(errorMessage);
|
|
11849
|
+
}
|
|
11850
|
+
this.loggerService.log("configValidationService validation passed");
|
|
11851
|
+
};
|
|
11852
|
+
}
|
|
11853
|
+
}
|
|
11854
|
+
|
|
11561
11855
|
{
|
|
11562
11856
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
11563
11857
|
}
|
|
@@ -11584,9 +11878,11 @@ class OutlineMarkdownService {
|
|
|
11584
11878
|
provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
|
|
11585
11879
|
}
|
|
11586
11880
|
{
|
|
11587
|
-
provide(TYPES.
|
|
11588
|
-
provide(TYPES.
|
|
11589
|
-
provide(TYPES.
|
|
11881
|
+
provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
|
|
11882
|
+
provide(TYPES.strategyCoreService, () => new StrategyCoreService());
|
|
11883
|
+
provide(TYPES.frameCoreService, () => new FrameCoreService());
|
|
11884
|
+
}
|
|
11885
|
+
{
|
|
11590
11886
|
provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
|
|
11591
11887
|
provide(TYPES.riskGlobalService, () => new RiskGlobalService());
|
|
11592
11888
|
provide(TYPES.optimizerGlobalService, () => new OptimizerGlobalService());
|
|
@@ -11625,6 +11921,7 @@ class OutlineMarkdownService {
|
|
|
11625
11921
|
provide(TYPES.sizingValidationService, () => new SizingValidationService());
|
|
11626
11922
|
provide(TYPES.riskValidationService, () => new RiskValidationService());
|
|
11627
11923
|
provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
|
|
11924
|
+
provide(TYPES.configValidationService, () => new ConfigValidationService());
|
|
11628
11925
|
}
|
|
11629
11926
|
{
|
|
11630
11927
|
provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
|
|
@@ -11655,10 +11952,12 @@ const schemaServices = {
|
|
|
11655
11952
|
riskSchemaService: inject(TYPES.riskSchemaService),
|
|
11656
11953
|
optimizerSchemaService: inject(TYPES.optimizerSchemaService),
|
|
11657
11954
|
};
|
|
11955
|
+
const coreServices = {
|
|
11956
|
+
exchangeCoreService: inject(TYPES.exchangeCoreService),
|
|
11957
|
+
strategyCoreService: inject(TYPES.strategyCoreService),
|
|
11958
|
+
frameCoreService: inject(TYPES.frameCoreService),
|
|
11959
|
+
};
|
|
11658
11960
|
const globalServices = {
|
|
11659
|
-
exchangeGlobalService: inject(TYPES.exchangeGlobalService),
|
|
11660
|
-
strategyGlobalService: inject(TYPES.strategyGlobalService),
|
|
11661
|
-
frameGlobalService: inject(TYPES.frameGlobalService),
|
|
11662
11961
|
sizingGlobalService: inject(TYPES.sizingGlobalService),
|
|
11663
11962
|
riskGlobalService: inject(TYPES.riskGlobalService),
|
|
11664
11963
|
optimizerGlobalService: inject(TYPES.optimizerGlobalService),
|
|
@@ -11697,6 +11996,7 @@ const validationServices = {
|
|
|
11697
11996
|
sizingValidationService: inject(TYPES.sizingValidationService),
|
|
11698
11997
|
riskValidationService: inject(TYPES.riskValidationService),
|
|
11699
11998
|
optimizerValidationService: inject(TYPES.optimizerValidationService),
|
|
11999
|
+
configValidationService: inject(TYPES.configValidationService),
|
|
11700
12000
|
};
|
|
11701
12001
|
const templateServices = {
|
|
11702
12002
|
optimizerTemplateService: inject(TYPES.optimizerTemplateService),
|
|
@@ -11706,6 +12006,7 @@ const backtest = {
|
|
|
11706
12006
|
...contextServices,
|
|
11707
12007
|
...connectionServices,
|
|
11708
12008
|
...schemaServices,
|
|
12009
|
+
...coreServices,
|
|
11709
12010
|
...globalServices,
|
|
11710
12011
|
...commandServices,
|
|
11711
12012
|
...logicPrivateServices,
|
|
@@ -11734,12 +12035,13 @@ var backtest$1 = backtest;
|
|
|
11734
12035
|
* });
|
|
11735
12036
|
* ```
|
|
11736
12037
|
*/
|
|
11737
|
-
|
|
12038
|
+
function setLogger(logger) {
|
|
11738
12039
|
backtest$1.loggerService.setLogger(logger);
|
|
11739
12040
|
}
|
|
11740
12041
|
/**
|
|
11741
12042
|
* Sets global configuration parameters for the framework.
|
|
11742
12043
|
* @param config - Partial configuration object to override default settings
|
|
12044
|
+
* @param _unsafe - Skip config validations - required for testbed
|
|
11743
12045
|
*
|
|
11744
12046
|
* @example
|
|
11745
12047
|
* ```typescript
|
|
@@ -11748,8 +12050,51 @@ async function setLogger(logger) {
|
|
|
11748
12050
|
* });
|
|
11749
12051
|
* ```
|
|
11750
12052
|
*/
|
|
11751
|
-
|
|
11752
|
-
Object.assign(
|
|
12053
|
+
function setConfig(config, _unsafe) {
|
|
12054
|
+
const prevConfig = Object.assign({}, GLOBAL_CONFIG);
|
|
12055
|
+
try {
|
|
12056
|
+
Object.assign(GLOBAL_CONFIG, config);
|
|
12057
|
+
!_unsafe && backtest$1.configValidationService.validate();
|
|
12058
|
+
}
|
|
12059
|
+
catch (error) {
|
|
12060
|
+
console.warn(`backtest-kit setConfig failed: ${functoolsKit.getErrorMessage(error)}`, config);
|
|
12061
|
+
Object.assign(GLOBAL_CONFIG, prevConfig);
|
|
12062
|
+
throw error;
|
|
12063
|
+
}
|
|
12064
|
+
}
|
|
12065
|
+
/**
|
|
12066
|
+
* Retrieves a copy of the current global configuration.
|
|
12067
|
+
*
|
|
12068
|
+
* Returns a shallow copy of the current GLOBAL_CONFIG to prevent accidental mutations.
|
|
12069
|
+
* Use this to inspect the current configuration state without modifying it.
|
|
12070
|
+
*
|
|
12071
|
+
* @returns {GlobalConfig} A copy of the current global configuration object
|
|
12072
|
+
*
|
|
12073
|
+
* @example
|
|
12074
|
+
* ```typescript
|
|
12075
|
+
* const currentConfig = getConfig();
|
|
12076
|
+
* console.log(currentConfig.CC_SCHEDULE_AWAIT_MINUTES);
|
|
12077
|
+
* ```
|
|
12078
|
+
*/
|
|
12079
|
+
function getConfig() {
|
|
12080
|
+
return Object.assign({}, GLOBAL_CONFIG);
|
|
12081
|
+
}
|
|
12082
|
+
/**
|
|
12083
|
+
* Retrieves the default configuration object for the framework.
|
|
12084
|
+
*
|
|
12085
|
+
* Returns a reference to the default configuration with all preset values.
|
|
12086
|
+
* Use this to see what configuration options are available and their default values.
|
|
12087
|
+
*
|
|
12088
|
+
* @returns {GlobalConfig} The default configuration object
|
|
12089
|
+
*
|
|
12090
|
+
* @example
|
|
12091
|
+
* ```typescript
|
|
12092
|
+
* const defaultConfig = getDefaultConfig();
|
|
12093
|
+
* console.log(defaultConfig.CC_SCHEDULE_AWAIT_MINUTES);
|
|
12094
|
+
* ```
|
|
12095
|
+
*/
|
|
12096
|
+
function getDefaultConfig() {
|
|
12097
|
+
return DEFAULT_CONFIG;
|
|
11753
12098
|
}
|
|
11754
12099
|
|
|
11755
12100
|
const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
|
|
@@ -13559,7 +13904,7 @@ class BacktestInstance {
|
|
|
13559
13904
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
13560
13905
|
}
|
|
13561
13906
|
{
|
|
13562
|
-
backtest$1.
|
|
13907
|
+
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
13563
13908
|
}
|
|
13564
13909
|
{
|
|
13565
13910
|
const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
@@ -13594,8 +13939,8 @@ class BacktestInstance {
|
|
|
13594
13939
|
});
|
|
13595
13940
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
13596
13941
|
return () => {
|
|
13597
|
-
backtest$1.
|
|
13598
|
-
backtest$1.
|
|
13942
|
+
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, true);
|
|
13943
|
+
backtest$1.strategyCoreService
|
|
13599
13944
|
.getPendingSignal(symbol, context.strategyName)
|
|
13600
13945
|
.then(async (pendingSignal) => {
|
|
13601
13946
|
if (pendingSignal) {
|
|
@@ -13636,7 +13981,7 @@ class BacktestInstance {
|
|
|
13636
13981
|
symbol,
|
|
13637
13982
|
strategyName,
|
|
13638
13983
|
});
|
|
13639
|
-
await backtest$1.
|
|
13984
|
+
await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
|
|
13640
13985
|
};
|
|
13641
13986
|
/**
|
|
13642
13987
|
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
@@ -14051,7 +14396,7 @@ class LiveInstance {
|
|
|
14051
14396
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
14052
14397
|
}
|
|
14053
14398
|
{
|
|
14054
|
-
backtest$1.
|
|
14399
|
+
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
14055
14400
|
}
|
|
14056
14401
|
{
|
|
14057
14402
|
const { riskName } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
@@ -14086,8 +14431,8 @@ class LiveInstance {
|
|
|
14086
14431
|
});
|
|
14087
14432
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
14088
14433
|
return () => {
|
|
14089
|
-
backtest$1.
|
|
14090
|
-
backtest$1.
|
|
14434
|
+
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, false);
|
|
14435
|
+
backtest$1.strategyCoreService
|
|
14091
14436
|
.getPendingSignal(symbol, context.strategyName)
|
|
14092
14437
|
.then(async (pendingSignal) => {
|
|
14093
14438
|
if (pendingSignal) {
|
|
@@ -14128,7 +14473,7 @@ class LiveInstance {
|
|
|
14128
14473
|
symbol,
|
|
14129
14474
|
strategyName,
|
|
14130
14475
|
});
|
|
14131
|
-
await backtest$1.
|
|
14476
|
+
await backtest$1.strategyCoreService.stop({ symbol, strategyName }, false);
|
|
14132
14477
|
};
|
|
14133
14478
|
/**
|
|
14134
14479
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
@@ -14809,7 +15154,7 @@ class WalkerInstance {
|
|
|
14809
15154
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
|
|
14810
15155
|
}
|
|
14811
15156
|
{
|
|
14812
|
-
backtest$1.
|
|
15157
|
+
backtest$1.strategyCoreService.clear({ symbol, strategyName });
|
|
14813
15158
|
}
|
|
14814
15159
|
{
|
|
14815
15160
|
const { riskName } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -14849,7 +15194,7 @@ class WalkerInstance {
|
|
|
14849
15194
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
14850
15195
|
return () => {
|
|
14851
15196
|
for (const strategyName of walkerSchema.strategies) {
|
|
14852
|
-
backtest$1.
|
|
15197
|
+
backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
|
|
14853
15198
|
walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
|
|
14854
15199
|
}
|
|
14855
15200
|
if (!this._isDone) {
|
|
@@ -14895,7 +15240,7 @@ class WalkerInstance {
|
|
|
14895
15240
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
14896
15241
|
for (const strategyName of walkerSchema.strategies) {
|
|
14897
15242
|
await walkerStopSubject.next({ symbol, strategyName, walkerName });
|
|
14898
|
-
await backtest$1.
|
|
15243
|
+
await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
|
|
14899
15244
|
}
|
|
14900
15245
|
};
|
|
14901
15246
|
/**
|
|
@@ -15868,7 +16213,9 @@ exports.formatPrice = formatPrice;
|
|
|
15868
16213
|
exports.formatQuantity = formatQuantity;
|
|
15869
16214
|
exports.getAveragePrice = getAveragePrice;
|
|
15870
16215
|
exports.getCandles = getCandles;
|
|
16216
|
+
exports.getConfig = getConfig;
|
|
15871
16217
|
exports.getDate = getDate;
|
|
16218
|
+
exports.getDefaultConfig = getDefaultConfig;
|
|
15872
16219
|
exports.getMode = getMode;
|
|
15873
16220
|
exports.lib = backtest;
|
|
15874
16221
|
exports.listExchanges = listExchanges;
|