backtest-kit 1.5.2 → 1.5.3
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/README.md +177 -7
- package/build/index.cjs +327 -113
- package/build/index.mjs +328 -114
- package/package.json +1 -1
- package/types.d.ts +117 -6
package/build/index.cjs
CHANGED
|
@@ -1675,6 +1675,8 @@ const walkerCompleteSubject = new functoolsKit.Subject();
|
|
|
1675
1675
|
/**
|
|
1676
1676
|
* Walker stop emitter for walker cancellation events.
|
|
1677
1677
|
* Emits when a walker comparison is stopped/cancelled.
|
|
1678
|
+
*
|
|
1679
|
+
* Includes walkerName to support multiple walkers running on the same symbol.
|
|
1678
1680
|
*/
|
|
1679
1681
|
const walkerStopSubject = new functoolsKit.Subject();
|
|
1680
1682
|
/**
|
|
@@ -2909,6 +2911,23 @@ class ClientStrategy {
|
|
|
2909
2911
|
});
|
|
2910
2912
|
return this._pendingSignal;
|
|
2911
2913
|
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Returns the stopped state of the strategy.
|
|
2916
|
+
*
|
|
2917
|
+
* Indicates whether the strategy has been explicitly stopped and should
|
|
2918
|
+
* not continue processing new ticks or signals.
|
|
2919
|
+
*
|
|
2920
|
+
* @param symbol - Trading pair symbol
|
|
2921
|
+
* @param strategyName - Name of the strategy
|
|
2922
|
+
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
2923
|
+
*/
|
|
2924
|
+
async getStopped(symbol, strategyName) {
|
|
2925
|
+
this.params.logger.debug("ClientStrategy getStopped", {
|
|
2926
|
+
symbol,
|
|
2927
|
+
strategyName,
|
|
2928
|
+
});
|
|
2929
|
+
return this._isStopped;
|
|
2930
|
+
}
|
|
2912
2931
|
/**
|
|
2913
2932
|
* Performs a single tick of strategy execution.
|
|
2914
2933
|
*
|
|
@@ -3151,18 +3170,24 @@ class ClientStrategy {
|
|
|
3151
3170
|
* // Existing signal will continue until natural close
|
|
3152
3171
|
* ```
|
|
3153
3172
|
*/
|
|
3154
|
-
async stop(symbol, strategyName) {
|
|
3173
|
+
async stop(symbol, strategyName, backtest) {
|
|
3155
3174
|
this.params.logger.debug("ClientStrategy stop", {
|
|
3156
3175
|
symbol,
|
|
3157
3176
|
strategyName,
|
|
3158
3177
|
hasPendingSignal: this._pendingSignal !== null,
|
|
3159
3178
|
hasScheduledSignal: this._scheduledSignal !== null,
|
|
3179
|
+
backtest,
|
|
3160
3180
|
});
|
|
3161
3181
|
this._isStopped = true;
|
|
3162
3182
|
// Clear scheduled signal if exists
|
|
3163
|
-
if (this._scheduledSignal) {
|
|
3164
|
-
|
|
3183
|
+
if (!this._scheduledSignal) {
|
|
3184
|
+
return;
|
|
3165
3185
|
}
|
|
3186
|
+
this._scheduledSignal = null;
|
|
3187
|
+
if (backtest) {
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
await PersistScheduleAdapter.writeScheduleData(this._scheduledSignal, this.params.execution.context.symbol, this.params.strategyName);
|
|
3166
3191
|
}
|
|
3167
3192
|
}
|
|
3168
3193
|
|
|
@@ -3245,6 +3270,24 @@ class StrategyConnectionService {
|
|
|
3245
3270
|
const strategy = this.getStrategy(symbol, strategyName);
|
|
3246
3271
|
return await strategy.getPendingSignal(symbol, strategyName);
|
|
3247
3272
|
};
|
|
3273
|
+
/**
|
|
3274
|
+
* Retrieves the stopped state of the strategy.
|
|
3275
|
+
*
|
|
3276
|
+
* Delegates to the underlying strategy instance to check if it has been
|
|
3277
|
+
* marked as stopped and should cease operation.
|
|
3278
|
+
*
|
|
3279
|
+
* @param symbol - Trading pair symbol
|
|
3280
|
+
* @param strategyName - Name of the strategy
|
|
3281
|
+
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
3282
|
+
*/
|
|
3283
|
+
this.getStopped = async (symbol, strategyName) => {
|
|
3284
|
+
this.loggerService.log("strategyConnectionService getStopped", {
|
|
3285
|
+
symbol,
|
|
3286
|
+
strategyName,
|
|
3287
|
+
});
|
|
3288
|
+
const strategy = this.getStrategy(symbol, strategyName);
|
|
3289
|
+
return await strategy.getStopped(symbol, strategyName);
|
|
3290
|
+
};
|
|
3248
3291
|
/**
|
|
3249
3292
|
* Executes live trading tick for current strategy.
|
|
3250
3293
|
*
|
|
@@ -3312,12 +3355,12 @@ class StrategyConnectionService {
|
|
|
3312
3355
|
* @param strategyName - Name of strategy to stop
|
|
3313
3356
|
* @returns Promise that resolves when stop flag is set
|
|
3314
3357
|
*/
|
|
3315
|
-
this.stop = async (ctx) => {
|
|
3358
|
+
this.stop = async (ctx, backtest) => {
|
|
3316
3359
|
this.loggerService.log("strategyConnectionService stop", {
|
|
3317
3360
|
ctx
|
|
3318
3361
|
});
|
|
3319
3362
|
const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
|
|
3320
|
-
await strategy.stop(ctx.symbol, ctx.strategyName);
|
|
3363
|
+
await strategy.stop(ctx.symbol, ctx.strategyName, backtest);
|
|
3321
3364
|
};
|
|
3322
3365
|
/**
|
|
3323
3366
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -4182,6 +4225,24 @@ class StrategyGlobalService {
|
|
|
4182
4225
|
await this.validate(symbol, strategyName);
|
|
4183
4226
|
return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
|
|
4184
4227
|
};
|
|
4228
|
+
/**
|
|
4229
|
+
* Checks if the strategy has been stopped.
|
|
4230
|
+
*
|
|
4231
|
+
* Validates strategy existence and delegates to connection service
|
|
4232
|
+
* to retrieve the stopped state from the strategy instance.
|
|
4233
|
+
*
|
|
4234
|
+
* @param symbol - Trading pair symbol
|
|
4235
|
+
* @param strategyName - Name of the strategy
|
|
4236
|
+
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
4237
|
+
*/
|
|
4238
|
+
this.getStopped = async (symbol, strategyName) => {
|
|
4239
|
+
this.loggerService.log("strategyGlobalService getStopped", {
|
|
4240
|
+
symbol,
|
|
4241
|
+
strategyName,
|
|
4242
|
+
});
|
|
4243
|
+
await this.validate(symbol, strategyName);
|
|
4244
|
+
return await this.strategyConnectionService.getStopped(symbol, strategyName);
|
|
4245
|
+
};
|
|
4185
4246
|
/**
|
|
4186
4247
|
* Checks signal status at a specific timestamp.
|
|
4187
4248
|
*
|
|
@@ -4248,12 +4309,13 @@ class StrategyGlobalService {
|
|
|
4248
4309
|
* @param strategyName - Name of strategy to stop
|
|
4249
4310
|
* @returns Promise that resolves when stop flag is set
|
|
4250
4311
|
*/
|
|
4251
|
-
this.stop = async (ctx) => {
|
|
4312
|
+
this.stop = async (ctx, backtest) => {
|
|
4252
4313
|
this.loggerService.log("strategyGlobalService stop", {
|
|
4253
4314
|
ctx,
|
|
4315
|
+
backtest,
|
|
4254
4316
|
});
|
|
4255
4317
|
await this.validate(ctx.symbol, ctx.strategyName);
|
|
4256
|
-
return await this.strategyConnectionService.stop(ctx);
|
|
4318
|
+
return await this.strategyConnectionService.stop(ctx, backtest);
|
|
4257
4319
|
};
|
|
4258
4320
|
/**
|
|
4259
4321
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -4945,6 +5007,16 @@ class BacktestLogicPrivateService {
|
|
|
4945
5007
|
progress: totalFrames > 0 ? i / totalFrames : 0,
|
|
4946
5008
|
});
|
|
4947
5009
|
}
|
|
5010
|
+
// Check if strategy should stop before processing next frame
|
|
5011
|
+
if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5012
|
+
this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
|
|
5013
|
+
symbol,
|
|
5014
|
+
when: when.toISOString(),
|
|
5015
|
+
processedFrames: i,
|
|
5016
|
+
totalFrames,
|
|
5017
|
+
});
|
|
5018
|
+
break;
|
|
5019
|
+
}
|
|
4948
5020
|
let result;
|
|
4949
5021
|
try {
|
|
4950
5022
|
result = await this.strategyGlobalService.tick(symbol, when, true);
|
|
@@ -4960,6 +5032,16 @@ class BacktestLogicPrivateService {
|
|
|
4960
5032
|
i++;
|
|
4961
5033
|
continue;
|
|
4962
5034
|
}
|
|
5035
|
+
// Check if strategy should stop when idle (no active signal)
|
|
5036
|
+
if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
5037
|
+
this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
|
|
5038
|
+
symbol,
|
|
5039
|
+
when: when.toISOString(),
|
|
5040
|
+
processedFrames: i,
|
|
5041
|
+
totalFrames,
|
|
5042
|
+
});
|
|
5043
|
+
break;
|
|
5044
|
+
}
|
|
4963
5045
|
// Если scheduled signal создан - обрабатываем через backtest()
|
|
4964
5046
|
if (result.action === "scheduled") {
|
|
4965
5047
|
const signalStartTime = performance.now();
|
|
@@ -5052,6 +5134,16 @@ class BacktestLogicPrivateService {
|
|
|
5052
5134
|
i++;
|
|
5053
5135
|
}
|
|
5054
5136
|
yield backtestResult;
|
|
5137
|
+
// Check if strategy should stop after signal is closed
|
|
5138
|
+
if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5139
|
+
this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
|
|
5140
|
+
symbol,
|
|
5141
|
+
signalId: backtestResult.signal.id,
|
|
5142
|
+
processedFrames: i,
|
|
5143
|
+
totalFrames,
|
|
5144
|
+
});
|
|
5145
|
+
break;
|
|
5146
|
+
}
|
|
5055
5147
|
}
|
|
5056
5148
|
// Если обычный сигнал открыт, вызываем backtest
|
|
5057
5149
|
if (result.action === "opened") {
|
|
@@ -5135,6 +5227,16 @@ class BacktestLogicPrivateService {
|
|
|
5135
5227
|
i++;
|
|
5136
5228
|
}
|
|
5137
5229
|
yield backtestResult;
|
|
5230
|
+
// Check if strategy should stop after signal is closed
|
|
5231
|
+
if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5232
|
+
this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
|
|
5233
|
+
symbol,
|
|
5234
|
+
signalId: backtestResult.signal.id,
|
|
5235
|
+
processedFrames: i,
|
|
5236
|
+
totalFrames,
|
|
5237
|
+
});
|
|
5238
|
+
break;
|
|
5239
|
+
}
|
|
5138
5240
|
}
|
|
5139
5241
|
// Track timeframe processing duration
|
|
5140
5242
|
const timeframeEndTime = performance.now();
|
|
@@ -5242,7 +5344,8 @@ class LiveLogicPrivateService {
|
|
|
5242
5344
|
this.loggerService.warn("liveLogicPrivateService tick failed, retrying after sleep", {
|
|
5243
5345
|
symbol,
|
|
5244
5346
|
when: when.toISOString(),
|
|
5245
|
-
error: functoolsKit.errorData(error),
|
|
5347
|
+
error: functoolsKit.errorData(error),
|
|
5348
|
+
message: functoolsKit.getErrorMessage(error),
|
|
5246
5349
|
});
|
|
5247
5350
|
await errorEmitter.next(error);
|
|
5248
5351
|
await functoolsKit.sleep(TICK_TTL);
|
|
@@ -5266,11 +5369,19 @@ class LiveLogicPrivateService {
|
|
|
5266
5369
|
backtest: false,
|
|
5267
5370
|
});
|
|
5268
5371
|
previousEventTimestamp = currentTimestamp;
|
|
5269
|
-
if (
|
|
5372
|
+
// Check if strategy should stop when idle (no active signal)
|
|
5373
|
+
if (result.action === "idle") {
|
|
5374
|
+
if (await functoolsKit.and(Promise.resolve(true), this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
5375
|
+
this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
|
|
5376
|
+
symbol,
|
|
5377
|
+
when: when.toISOString(),
|
|
5378
|
+
});
|
|
5379
|
+
break;
|
|
5380
|
+
}
|
|
5270
5381
|
await functoolsKit.sleep(TICK_TTL);
|
|
5271
5382
|
continue;
|
|
5272
5383
|
}
|
|
5273
|
-
if (result.action === "
|
|
5384
|
+
if (result.action === "active") {
|
|
5274
5385
|
await functoolsKit.sleep(TICK_TTL);
|
|
5275
5386
|
continue;
|
|
5276
5387
|
}
|
|
@@ -5280,12 +5391,21 @@ class LiveLogicPrivateService {
|
|
|
5280
5391
|
}
|
|
5281
5392
|
// Yield opened, closed results
|
|
5282
5393
|
yield result;
|
|
5394
|
+
// Check if strategy should stop after signal is closed
|
|
5395
|
+
if (result.action === "closed") {
|
|
5396
|
+
if (await this.strategyGlobalService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
5397
|
+
this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
|
|
5398
|
+
symbol,
|
|
5399
|
+
signalId: result.signal.id,
|
|
5400
|
+
});
|
|
5401
|
+
break;
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5283
5404
|
await functoolsKit.sleep(TICK_TTL);
|
|
5284
5405
|
}
|
|
5285
5406
|
}
|
|
5286
5407
|
}
|
|
5287
5408
|
|
|
5288
|
-
const CANCEL_SYMBOL = Symbol("CANCEL_SYMBOL");
|
|
5289
5409
|
/**
|
|
5290
5410
|
* Private service for walker orchestration (strategy comparison).
|
|
5291
5411
|
*
|
|
@@ -5343,112 +5463,121 @@ class WalkerLogicPrivateService {
|
|
|
5343
5463
|
let strategiesTested = 0;
|
|
5344
5464
|
let bestMetric = null;
|
|
5345
5465
|
let bestStrategy = null;
|
|
5346
|
-
|
|
5347
|
-
const
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
.
|
|
5355
|
-
.
|
|
5356
|
-
// Run backtest for each strategy
|
|
5357
|
-
for (const strategyName of strategies) {
|
|
5358
|
-
// Call onStrategyStart callback if provided
|
|
5359
|
-
if (walkerSchema.callbacks?.onStrategyStart) {
|
|
5360
|
-
walkerSchema.callbacks.onStrategyStart(strategyName, symbol);
|
|
5361
|
-
}
|
|
5362
|
-
this.loggerService.info("walkerLogicPrivateService testing strategy", {
|
|
5363
|
-
strategyName,
|
|
5466
|
+
// Track stopped strategies in Set for efficient lookup
|
|
5467
|
+
const stoppedStrategies = new Set();
|
|
5468
|
+
// Subscribe to stop signals and collect them in Set
|
|
5469
|
+
// Filter by both symbol AND walkerName to support multiple walkers on same symbol
|
|
5470
|
+
// connect() returns destructor function
|
|
5471
|
+
const unsubscribe = walkerStopSubject
|
|
5472
|
+
.filter((data) => data.symbol === symbol && data.walkerName === context.walkerName)
|
|
5473
|
+
.connect((data) => {
|
|
5474
|
+
stoppedStrategies.add(data.strategyName);
|
|
5475
|
+
this.loggerService.info("walkerLogicPrivateService received stop signal for strategy", {
|
|
5364
5476
|
symbol,
|
|
5477
|
+
walkerName: context.walkerName,
|
|
5478
|
+
strategyName: data.strategyName,
|
|
5479
|
+
stoppedCount: stoppedStrategies.size,
|
|
5365
5480
|
});
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5481
|
+
});
|
|
5482
|
+
try {
|
|
5483
|
+
// Run backtest for each strategy
|
|
5484
|
+
for (const strategyName of strategies) {
|
|
5485
|
+
// Check if this strategy should be stopped before starting
|
|
5486
|
+
if (stoppedStrategies.has(strategyName)) {
|
|
5487
|
+
this.loggerService.info("walkerLogicPrivateService skipping stopped strategy", {
|
|
5488
|
+
symbol,
|
|
5489
|
+
strategyName,
|
|
5490
|
+
});
|
|
5491
|
+
break;
|
|
5492
|
+
}
|
|
5493
|
+
// Call onStrategyStart callback if provided
|
|
5494
|
+
if (walkerSchema.callbacks?.onStrategyStart) {
|
|
5495
|
+
walkerSchema.callbacks.onStrategyStart(strategyName, symbol);
|
|
5496
|
+
}
|
|
5497
|
+
this.loggerService.info("walkerLogicPrivateService testing strategy", {
|
|
5382
5498
|
strategyName,
|
|
5383
5499
|
symbol,
|
|
5384
|
-
error: functoolsKit.errorData(error), message: functoolsKit.getErrorMessage(error),
|
|
5385
5500
|
});
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5501
|
+
const iterator = this.backtestLogicPublicService.run(symbol, {
|
|
5502
|
+
strategyName,
|
|
5503
|
+
exchangeName: context.exchangeName,
|
|
5504
|
+
frameName: context.frameName,
|
|
5505
|
+
});
|
|
5506
|
+
try {
|
|
5507
|
+
await functoolsKit.resolveDocuments(iterator);
|
|
5390
5508
|
}
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5509
|
+
catch (error) {
|
|
5510
|
+
console.warn(`walkerLogicPrivateService backtest failed symbol=${symbol} strategyName=${strategyName} exchangeName=${context.exchangeName}`);
|
|
5511
|
+
this.loggerService.warn("walkerLogicPrivateService backtest failed for strategy, skipping", {
|
|
5512
|
+
strategyName,
|
|
5513
|
+
symbol,
|
|
5514
|
+
error: functoolsKit.errorData(error), message: functoolsKit.getErrorMessage(error),
|
|
5515
|
+
});
|
|
5516
|
+
await errorEmitter.next(error);
|
|
5517
|
+
// Call onStrategyError callback if provided
|
|
5518
|
+
if (walkerSchema.callbacks?.onStrategyError) {
|
|
5519
|
+
walkerSchema.callbacks.onStrategyError(strategyName, symbol, error);
|
|
5520
|
+
}
|
|
5521
|
+
continue;
|
|
5522
|
+
}
|
|
5523
|
+
this.loggerService.info("walkerLogicPrivateService backtest complete", {
|
|
5524
|
+
strategyName,
|
|
5525
|
+
symbol,
|
|
5396
5526
|
});
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
// Call onStrategyComplete callback if provided
|
|
5447
|
-
if (walkerSchema.callbacks?.onStrategyComplete) {
|
|
5448
|
-
walkerSchema.callbacks.onStrategyComplete(strategyName, symbol, stats, metricValue);
|
|
5527
|
+
// Get statistics from BacktestMarkdownService
|
|
5528
|
+
const stats = await this.backtestMarkdownService.getData(symbol, strategyName);
|
|
5529
|
+
// Extract metric value
|
|
5530
|
+
const value = stats[metric];
|
|
5531
|
+
const metricValue = value !== null &&
|
|
5532
|
+
value !== undefined &&
|
|
5533
|
+
typeof value === "number" &&
|
|
5534
|
+
!isNaN(value) &&
|
|
5535
|
+
isFinite(value)
|
|
5536
|
+
? value
|
|
5537
|
+
: null;
|
|
5538
|
+
// Update best strategy if needed
|
|
5539
|
+
const isBetter = bestMetric === null ||
|
|
5540
|
+
(metricValue !== null && metricValue > bestMetric);
|
|
5541
|
+
if (isBetter && metricValue !== null) {
|
|
5542
|
+
bestMetric = metricValue;
|
|
5543
|
+
bestStrategy = strategyName;
|
|
5544
|
+
}
|
|
5545
|
+
strategiesTested++;
|
|
5546
|
+
const walkerContract = {
|
|
5547
|
+
walkerName: context.walkerName,
|
|
5548
|
+
exchangeName: context.exchangeName,
|
|
5549
|
+
frameName: context.frameName,
|
|
5550
|
+
symbol,
|
|
5551
|
+
strategyName,
|
|
5552
|
+
stats,
|
|
5553
|
+
metricValue,
|
|
5554
|
+
metric,
|
|
5555
|
+
bestMetric,
|
|
5556
|
+
bestStrategy,
|
|
5557
|
+
strategiesTested,
|
|
5558
|
+
totalStrategies: strategies.length,
|
|
5559
|
+
};
|
|
5560
|
+
// Emit progress event
|
|
5561
|
+
await progressWalkerEmitter.next({
|
|
5562
|
+
walkerName: context.walkerName,
|
|
5563
|
+
exchangeName: context.exchangeName,
|
|
5564
|
+
frameName: context.frameName,
|
|
5565
|
+
symbol,
|
|
5566
|
+
totalStrategies: strategies.length,
|
|
5567
|
+
processedStrategies: strategiesTested,
|
|
5568
|
+
progress: strategies.length > 0 ? strategiesTested / strategies.length : 0,
|
|
5569
|
+
});
|
|
5570
|
+
// Call onStrategyComplete callback if provided
|
|
5571
|
+
if (walkerSchema.callbacks?.onStrategyComplete) {
|
|
5572
|
+
await walkerSchema.callbacks.onStrategyComplete(strategyName, symbol, stats, metricValue);
|
|
5573
|
+
}
|
|
5574
|
+
await walkerEmitter.next(walkerContract);
|
|
5575
|
+
yield walkerContract;
|
|
5449
5576
|
}
|
|
5450
|
-
|
|
5451
|
-
|
|
5577
|
+
}
|
|
5578
|
+
finally {
|
|
5579
|
+
// Unsubscribe from stop signals by calling destructor
|
|
5580
|
+
unsubscribe();
|
|
5452
5581
|
}
|
|
5453
5582
|
const finalResults = {
|
|
5454
5583
|
walkerName: context.walkerName,
|
|
@@ -13270,6 +13399,7 @@ async function dumpSignal(signalId, history, signal, outputDir = "./dump/strateg
|
|
|
13270
13399
|
|
|
13271
13400
|
const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
|
|
13272
13401
|
const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
|
|
13402
|
+
const BACKTEST_METHOD_NAME_STOP = "BacktestUtils.stop";
|
|
13273
13403
|
const BACKTEST_METHOD_NAME_GET_REPORT = "BacktestUtils.getReport";
|
|
13274
13404
|
const BACKTEST_METHOD_NAME_DUMP = "BacktestUtils.dump";
|
|
13275
13405
|
/**
|
|
@@ -13364,7 +13494,7 @@ class BacktestUtils {
|
|
|
13364
13494
|
};
|
|
13365
13495
|
task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
13366
13496
|
return () => {
|
|
13367
|
-
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
13497
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
|
|
13368
13498
|
backtest$1.strategyGlobalService
|
|
13369
13499
|
.getPendingSignal(symbol, context.strategyName)
|
|
13370
13500
|
.then(async (pendingSignal) => {
|
|
@@ -13384,6 +13514,30 @@ class BacktestUtils {
|
|
|
13384
13514
|
isStopped = true;
|
|
13385
13515
|
};
|
|
13386
13516
|
};
|
|
13517
|
+
/**
|
|
13518
|
+
* Stops the strategy from generating new signals.
|
|
13519
|
+
*
|
|
13520
|
+
* Sets internal flag to prevent strategy from opening new signals.
|
|
13521
|
+
* Current active signal (if any) will complete normally.
|
|
13522
|
+
* Backtest will stop at the next safe point (idle state or after signal closes).
|
|
13523
|
+
*
|
|
13524
|
+
* @param symbol - Trading pair symbol
|
|
13525
|
+
* @param strategyName - Strategy name to stop
|
|
13526
|
+
* @returns Promise that resolves when stop flag is set
|
|
13527
|
+
*
|
|
13528
|
+
* @example
|
|
13529
|
+
* ```typescript
|
|
13530
|
+
* // Stop strategy after some condition
|
|
13531
|
+
* await Backtest.stop("BTCUSDT", "my-strategy");
|
|
13532
|
+
* ```
|
|
13533
|
+
*/
|
|
13534
|
+
this.stop = async (symbol, strategyName) => {
|
|
13535
|
+
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_STOP, {
|
|
13536
|
+
symbol,
|
|
13537
|
+
strategyName,
|
|
13538
|
+
});
|
|
13539
|
+
await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
|
|
13540
|
+
};
|
|
13387
13541
|
/**
|
|
13388
13542
|
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
13389
13543
|
*
|
|
@@ -13472,6 +13626,7 @@ const Backtest = new BacktestUtils();
|
|
|
13472
13626
|
|
|
13473
13627
|
const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
|
|
13474
13628
|
const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
|
|
13629
|
+
const LIVE_METHOD_NAME_STOP = "LiveUtils.stop";
|
|
13475
13630
|
const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
|
|
13476
13631
|
const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
|
|
13477
13632
|
/**
|
|
@@ -13579,7 +13734,7 @@ class LiveUtils {
|
|
|
13579
13734
|
};
|
|
13580
13735
|
task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
13581
13736
|
return () => {
|
|
13582
|
-
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
13737
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
|
|
13583
13738
|
backtest$1.strategyGlobalService
|
|
13584
13739
|
.getPendingSignal(symbol, context.strategyName)
|
|
13585
13740
|
.then(async (pendingSignal) => {
|
|
@@ -13599,6 +13754,30 @@ class LiveUtils {
|
|
|
13599
13754
|
isStopped = true;
|
|
13600
13755
|
};
|
|
13601
13756
|
};
|
|
13757
|
+
/**
|
|
13758
|
+
* Stops the strategy from generating new signals.
|
|
13759
|
+
*
|
|
13760
|
+
* Sets internal flag to prevent strategy from opening new signals.
|
|
13761
|
+
* Current active signal (if any) will complete normally.
|
|
13762
|
+
* Live trading will stop at the next safe point (idle/closed state).
|
|
13763
|
+
*
|
|
13764
|
+
* @param symbol - Trading pair symbol
|
|
13765
|
+
* @param strategyName - Strategy name to stop
|
|
13766
|
+
* @returns Promise that resolves when stop flag is set
|
|
13767
|
+
*
|
|
13768
|
+
* @example
|
|
13769
|
+
* ```typescript
|
|
13770
|
+
* // Stop live trading gracefully
|
|
13771
|
+
* await Live.stop("BTCUSDT", "my-strategy");
|
|
13772
|
+
* ```
|
|
13773
|
+
*/
|
|
13774
|
+
this.stop = async (symbol, strategyName) => {
|
|
13775
|
+
backtest$1.loggerService.info(LIVE_METHOD_NAME_STOP, {
|
|
13776
|
+
symbol,
|
|
13777
|
+
strategyName,
|
|
13778
|
+
});
|
|
13779
|
+
await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
|
|
13780
|
+
};
|
|
13602
13781
|
/**
|
|
13603
13782
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
13604
13783
|
*
|
|
@@ -13906,6 +14085,7 @@ class Performance {
|
|
|
13906
14085
|
|
|
13907
14086
|
const WALKER_METHOD_NAME_RUN = "WalkerUtils.run";
|
|
13908
14087
|
const WALKER_METHOD_NAME_BACKGROUND = "WalkerUtils.background";
|
|
14088
|
+
const WALKER_METHOD_NAME_STOP = "WalkerUtils.stop";
|
|
13909
14089
|
const WALKER_METHOD_NAME_GET_DATA = "WalkerUtils.getData";
|
|
13910
14090
|
const WALKER_METHOD_NAME_GET_REPORT = "WalkerUtils.getReport";
|
|
13911
14091
|
const WALKER_METHOD_NAME_DUMP = "WalkerUtils.dump";
|
|
@@ -14016,8 +14196,8 @@ class WalkerUtils {
|
|
|
14016
14196
|
task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
14017
14197
|
return () => {
|
|
14018
14198
|
for (const strategyName of walkerSchema.strategies) {
|
|
14019
|
-
backtest$1.strategyGlobalService.stop({ symbol, strategyName });
|
|
14020
|
-
walkerStopSubject.next({ symbol, strategyName });
|
|
14199
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
|
|
14200
|
+
walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
|
|
14021
14201
|
}
|
|
14022
14202
|
if (!isDone) {
|
|
14023
14203
|
doneWalkerSubject.next({
|
|
@@ -14031,6 +14211,40 @@ class WalkerUtils {
|
|
|
14031
14211
|
isStopped = true;
|
|
14032
14212
|
};
|
|
14033
14213
|
};
|
|
14214
|
+
/**
|
|
14215
|
+
* Stops all strategies in the walker from generating new signals.
|
|
14216
|
+
*
|
|
14217
|
+
* Iterates through all strategies defined in walker schema and:
|
|
14218
|
+
* 1. Sends stop signal via walkerStopSubject (interrupts current running strategy)
|
|
14219
|
+
* 2. Sets internal stop flag for each strategy (prevents new signals)
|
|
14220
|
+
*
|
|
14221
|
+
* Current active signals (if any) will complete normally.
|
|
14222
|
+
* Walker will stop at the next safe point.
|
|
14223
|
+
*
|
|
14224
|
+
* Supports multiple walkers running on the same symbol simultaneously.
|
|
14225
|
+
* Stop signal is filtered by walkerName to prevent interference.
|
|
14226
|
+
*
|
|
14227
|
+
* @param symbol - Trading pair symbol
|
|
14228
|
+
* @param walkerName - Walker name to stop
|
|
14229
|
+
* @returns Promise that resolves when all stop flags are set
|
|
14230
|
+
*
|
|
14231
|
+
* @example
|
|
14232
|
+
* ```typescript
|
|
14233
|
+
* // Stop walker and all its strategies
|
|
14234
|
+
* await Walker.stop("BTCUSDT", "my-walker");
|
|
14235
|
+
* ```
|
|
14236
|
+
*/
|
|
14237
|
+
this.stop = async (symbol, walkerName) => {
|
|
14238
|
+
backtest$1.loggerService.info(WALKER_METHOD_NAME_STOP, {
|
|
14239
|
+
symbol,
|
|
14240
|
+
walkerName,
|
|
14241
|
+
});
|
|
14242
|
+
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
14243
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
14244
|
+
await walkerStopSubject.next({ symbol, strategyName, walkerName });
|
|
14245
|
+
await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
|
|
14246
|
+
}
|
|
14247
|
+
};
|
|
14034
14248
|
/**
|
|
14035
14249
|
* Gets walker results data from all strategy comparisons.
|
|
14036
14250
|
*
|