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