backtest-kit 1.5.2 → 1.5.4
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 +1155 -268
- package/build/index.mjs +1156 -269
- package/package.json +2 -2
- package/types.d.ts +186 -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, singlerun } from 'functools-kit';
|
|
4
4
|
import fs, { mkdir, writeFile } from 'fs/promises';
|
|
5
5
|
import path, { join } from 'path';
|
|
6
6
|
import crypto from 'crypto';
|
|
@@ -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;
|
|
3183
|
+
}
|
|
3184
|
+
this._scheduledSignal = null;
|
|
3185
|
+
if (backtest) {
|
|
3186
|
+
return;
|
|
3163
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,19 +13397,53 @@ 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";
|
|
13403
|
+
const BACKTEST_METHOD_NAME_TASK = "BacktestUtils.task";
|
|
13404
|
+
const BACKTEST_METHOD_NAME_GET_STATUS = "BacktestUtils.getStatus";
|
|
13273
13405
|
/**
|
|
13274
|
-
*
|
|
13406
|
+
* Internal task function that runs backtest and handles completion.
|
|
13407
|
+
* Consumes backtest results and updates instance state flags.
|
|
13275
13408
|
*
|
|
13276
|
-
*
|
|
13277
|
-
*
|
|
13409
|
+
* @param symbol - Trading pair symbol
|
|
13410
|
+
* @param context - Execution context with strategy, exchange, and frame names
|
|
13411
|
+
* @param self - BacktestInstance reference for state management
|
|
13412
|
+
* @returns Promise that resolves when backtest completes
|
|
13413
|
+
*
|
|
13414
|
+
* @internal
|
|
13415
|
+
*/
|
|
13416
|
+
const INSTANCE_TASK_FN$2 = async (symbol, context, self) => {
|
|
13417
|
+
{
|
|
13418
|
+
self._isStopped = false;
|
|
13419
|
+
self._isDone = false;
|
|
13420
|
+
}
|
|
13421
|
+
for await (const _ of self.run(symbol, context)) {
|
|
13422
|
+
if (self._isStopped) {
|
|
13423
|
+
break;
|
|
13424
|
+
}
|
|
13425
|
+
}
|
|
13426
|
+
if (!self._isDone) {
|
|
13427
|
+
await doneBacktestSubject.next({
|
|
13428
|
+
exchangeName: context.exchangeName,
|
|
13429
|
+
strategyName: context.strategyName,
|
|
13430
|
+
backtest: true,
|
|
13431
|
+
symbol,
|
|
13432
|
+
});
|
|
13433
|
+
}
|
|
13434
|
+
self._isDone = true;
|
|
13435
|
+
};
|
|
13436
|
+
/**
|
|
13437
|
+
* Instance class for backtest operations on a specific symbol-strategy pair.
|
|
13438
|
+
*
|
|
13439
|
+
* Provides isolated backtest execution and reporting for a single symbol-strategy combination.
|
|
13440
|
+
* Each instance maintains its own state and context.
|
|
13278
13441
|
*
|
|
13279
13442
|
* @example
|
|
13280
13443
|
* ```typescript
|
|
13281
|
-
*
|
|
13444
|
+
* const instance = new BacktestInstance("BTCUSDT", "my-strategy");
|
|
13282
13445
|
*
|
|
13283
|
-
* for await (const result of
|
|
13446
|
+
* for await (const result of instance.run("BTCUSDT", {
|
|
13284
13447
|
* strategyName: "my-strategy",
|
|
13285
13448
|
* exchangeName: "my-exchange",
|
|
13286
13449
|
* frameName: "1d-backtest"
|
|
@@ -13289,8 +13452,57 @@ const BACKTEST_METHOD_NAME_DUMP = "BacktestUtils.dump";
|
|
|
13289
13452
|
* }
|
|
13290
13453
|
* ```
|
|
13291
13454
|
*/
|
|
13292
|
-
class
|
|
13293
|
-
|
|
13455
|
+
class BacktestInstance {
|
|
13456
|
+
/**
|
|
13457
|
+
* Creates a new BacktestInstance for a specific symbol-strategy pair.
|
|
13458
|
+
*
|
|
13459
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
13460
|
+
* @param strategyName - Strategy name for this backtest instance
|
|
13461
|
+
*/
|
|
13462
|
+
constructor(symbol, strategyName) {
|
|
13463
|
+
this.symbol = symbol;
|
|
13464
|
+
this.strategyName = strategyName;
|
|
13465
|
+
/** Internal flag indicating if backtest was stopped manually */
|
|
13466
|
+
this._isStopped = false;
|
|
13467
|
+
/** Internal flag indicating if backtest task completed */
|
|
13468
|
+
this._isDone = false;
|
|
13469
|
+
/**
|
|
13470
|
+
* Internal singlerun task that executes the backtest.
|
|
13471
|
+
* Ensures only one backtest run per instance using singlerun wrapper.
|
|
13472
|
+
*
|
|
13473
|
+
* @param symbol - Trading pair symbol
|
|
13474
|
+
* @param context - Execution context with strategy, exchange, and frame names
|
|
13475
|
+
* @returns Promise that resolves when backtest completes
|
|
13476
|
+
*
|
|
13477
|
+
* @internal
|
|
13478
|
+
*/
|
|
13479
|
+
this.task = singlerun(async (symbol, context) => {
|
|
13480
|
+
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_TASK, {
|
|
13481
|
+
symbol,
|
|
13482
|
+
context,
|
|
13483
|
+
});
|
|
13484
|
+
return await INSTANCE_TASK_FN$2(symbol, context, this);
|
|
13485
|
+
});
|
|
13486
|
+
/**
|
|
13487
|
+
* Gets the current status of this backtest instance.
|
|
13488
|
+
*
|
|
13489
|
+
* @returns Promise resolving to status object with symbol, strategyName, and task status
|
|
13490
|
+
*
|
|
13491
|
+
* @example
|
|
13492
|
+
* ```typescript
|
|
13493
|
+
* const instance = new BacktestInstance("BTCUSDT", "my-strategy");
|
|
13494
|
+
* const status = await instance.getStatus();
|
|
13495
|
+
* console.log(status.status); // "idle", "running", or "done"
|
|
13496
|
+
* ```
|
|
13497
|
+
*/
|
|
13498
|
+
this.getStatus = async () => {
|
|
13499
|
+
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_STATUS);
|
|
13500
|
+
return {
|
|
13501
|
+
symbol: this.symbol,
|
|
13502
|
+
strategyName: this.strategyName,
|
|
13503
|
+
status: this.task.getStatus(),
|
|
13504
|
+
};
|
|
13505
|
+
};
|
|
13294
13506
|
/**
|
|
13295
13507
|
* Runs backtest for a symbol with context propagation.
|
|
13296
13508
|
*
|
|
@@ -13328,13 +13540,12 @@ class BacktestUtils {
|
|
|
13328
13540
|
*
|
|
13329
13541
|
* @example
|
|
13330
13542
|
* ```typescript
|
|
13331
|
-
*
|
|
13332
|
-
*
|
|
13543
|
+
* const instance = new BacktestInstance();
|
|
13544
|
+
* const cancel = instance.background("BTCUSDT", {
|
|
13333
13545
|
* strategyName: "my-strategy",
|
|
13334
13546
|
* exchangeName: "my-exchange",
|
|
13335
13547
|
* frameName: "1d-backtest"
|
|
13336
13548
|
* });
|
|
13337
|
-
* console.log("Backtest completed");
|
|
13338
13549
|
* ```
|
|
13339
13550
|
*/
|
|
13340
13551
|
this.background = (symbol, context) => {
|
|
@@ -13342,34 +13553,16 @@ class BacktestUtils {
|
|
|
13342
13553
|
symbol,
|
|
13343
13554
|
context,
|
|
13344
13555
|
});
|
|
13345
|
-
|
|
13346
|
-
let isDone = false;
|
|
13347
|
-
const task = async () => {
|
|
13348
|
-
for await (const _ of this.run(symbol, context)) {
|
|
13349
|
-
if (isStopped) {
|
|
13350
|
-
break;
|
|
13351
|
-
}
|
|
13352
|
-
}
|
|
13353
|
-
if (!isDone) {
|
|
13354
|
-
await doneBacktestSubject.next({
|
|
13355
|
-
exchangeName: context.exchangeName,
|
|
13356
|
-
strategyName: context.strategyName,
|
|
13357
|
-
backtest: true,
|
|
13358
|
-
symbol,
|
|
13359
|
-
});
|
|
13360
|
-
}
|
|
13361
|
-
isDone = true;
|
|
13362
|
-
};
|
|
13363
|
-
task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
13556
|
+
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
13364
13557
|
return () => {
|
|
13365
|
-
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
13558
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, true);
|
|
13366
13559
|
backtest$1.strategyGlobalService
|
|
13367
13560
|
.getPendingSignal(symbol, context.strategyName)
|
|
13368
13561
|
.then(async (pendingSignal) => {
|
|
13369
13562
|
if (pendingSignal) {
|
|
13370
13563
|
return;
|
|
13371
13564
|
}
|
|
13372
|
-
if (!
|
|
13565
|
+
if (!this._isDone) {
|
|
13373
13566
|
await doneBacktestSubject.next({
|
|
13374
13567
|
exchangeName: context.exchangeName,
|
|
13375
13568
|
strategyName: context.strategyName,
|
|
@@ -13377,11 +13570,35 @@ class BacktestUtils {
|
|
|
13377
13570
|
symbol,
|
|
13378
13571
|
});
|
|
13379
13572
|
}
|
|
13380
|
-
|
|
13573
|
+
this._isDone = true;
|
|
13381
13574
|
});
|
|
13382
|
-
|
|
13575
|
+
this._isStopped = true;
|
|
13383
13576
|
};
|
|
13384
13577
|
};
|
|
13578
|
+
/**
|
|
13579
|
+
* Stops the strategy from generating new signals.
|
|
13580
|
+
*
|
|
13581
|
+
* Sets internal flag to prevent strategy from opening new signals.
|
|
13582
|
+
* Current active signal (if any) will complete normally.
|
|
13583
|
+
* Backtest will stop at the next safe point (idle state or after signal closes).
|
|
13584
|
+
*
|
|
13585
|
+
* @param symbol - Trading pair symbol
|
|
13586
|
+
* @param strategyName - Strategy name to stop
|
|
13587
|
+
* @returns Promise that resolves when stop flag is set
|
|
13588
|
+
*
|
|
13589
|
+
* @example
|
|
13590
|
+
* ```typescript
|
|
13591
|
+
* const instance = new BacktestInstance();
|
|
13592
|
+
* await instance.stop("BTCUSDT", "my-strategy");
|
|
13593
|
+
* ```
|
|
13594
|
+
*/
|
|
13595
|
+
this.stop = async (symbol, strategyName) => {
|
|
13596
|
+
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_STOP, {
|
|
13597
|
+
symbol,
|
|
13598
|
+
strategyName,
|
|
13599
|
+
});
|
|
13600
|
+
await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
|
|
13601
|
+
};
|
|
13385
13602
|
/**
|
|
13386
13603
|
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
13387
13604
|
*
|
|
@@ -13391,7 +13608,8 @@ class BacktestUtils {
|
|
|
13391
13608
|
*
|
|
13392
13609
|
* @example
|
|
13393
13610
|
* ```typescript
|
|
13394
|
-
* const
|
|
13611
|
+
* const instance = new BacktestInstance();
|
|
13612
|
+
* const stats = await instance.getData("BTCUSDT", "my-strategy");
|
|
13395
13613
|
* console.log(stats.sharpeRatio, stats.winRate);
|
|
13396
13614
|
* ```
|
|
13397
13615
|
*/
|
|
@@ -13411,7 +13629,8 @@ class BacktestUtils {
|
|
|
13411
13629
|
*
|
|
13412
13630
|
* @example
|
|
13413
13631
|
* ```typescript
|
|
13414
|
-
* const
|
|
13632
|
+
* const instance = new BacktestInstance();
|
|
13633
|
+
* const markdown = await instance.getReport("BTCUSDT", "my-strategy");
|
|
13415
13634
|
* console.log(markdown);
|
|
13416
13635
|
* ```
|
|
13417
13636
|
*/
|
|
@@ -13431,11 +13650,12 @@ class BacktestUtils {
|
|
|
13431
13650
|
*
|
|
13432
13651
|
* @example
|
|
13433
13652
|
* ```typescript
|
|
13653
|
+
* const instance = new BacktestInstance();
|
|
13434
13654
|
* // Save to default path: ./dump/backtest/my-strategy.md
|
|
13435
|
-
* await
|
|
13655
|
+
* await instance.dump("BTCUSDT", "my-strategy");
|
|
13436
13656
|
*
|
|
13437
13657
|
* // Save to custom path: ./custom/path/my-strategy.md
|
|
13438
|
-
* await
|
|
13658
|
+
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
13439
13659
|
* ```
|
|
13440
13660
|
*/
|
|
13441
13661
|
this.dump = async (symbol, strategyName, path) => {
|
|
@@ -13449,7 +13669,10 @@ class BacktestUtils {
|
|
|
13449
13669
|
}
|
|
13450
13670
|
}
|
|
13451
13671
|
/**
|
|
13452
|
-
*
|
|
13672
|
+
* Utility class for backtest operations.
|
|
13673
|
+
*
|
|
13674
|
+
* Provides simplified access to backtestCommandService.run() with logging.
|
|
13675
|
+
* Exported as singleton instance for convenient usage.
|
|
13453
13676
|
*
|
|
13454
13677
|
* @example
|
|
13455
13678
|
* ```typescript
|
|
@@ -13460,49 +13683,276 @@ class BacktestUtils {
|
|
|
13460
13683
|
* exchangeName: "my-exchange",
|
|
13461
13684
|
* frameName: "1d-backtest"
|
|
13462
13685
|
* })) {
|
|
13463
|
-
*
|
|
13464
|
-
* console.log("PNL:", result.pnl.pnlPercentage);
|
|
13465
|
-
* }
|
|
13686
|
+
* console.log("Closed signal PNL:", result.pnl.pnlPercentage);
|
|
13466
13687
|
* }
|
|
13467
13688
|
* ```
|
|
13468
13689
|
*/
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
|
|
13472
|
-
const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
|
|
13473
|
-
const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
|
|
13474
|
-
const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
|
|
13475
|
-
/**
|
|
13476
|
-
* Utility class for live trading operations.
|
|
13477
|
-
*
|
|
13478
|
-
* Provides simplified access to liveCommandService.run() with logging.
|
|
13479
|
-
* Exported as singleton instance for convenient usage.
|
|
13480
|
-
*
|
|
13481
|
-
* Features:
|
|
13482
|
-
* - Infinite async generator (never completes)
|
|
13483
|
-
* - Crash recovery via persisted state
|
|
13484
|
-
* - Real-time progression with Date.now()
|
|
13485
|
-
*
|
|
13486
|
-
* @example
|
|
13487
|
-
* ```typescript
|
|
13488
|
-
* import { Live } from "./classes/Live";
|
|
13489
|
-
*
|
|
13490
|
-
* // Infinite loop - use Ctrl+C to stop
|
|
13491
|
-
* for await (const result of Live.run("BTCUSDT", {
|
|
13492
|
-
* strategyName: "my-strategy",
|
|
13493
|
-
* exchangeName: "my-exchange",
|
|
13494
|
-
* frameName: ""
|
|
13495
|
-
* })) {
|
|
13496
|
-
* if (result.action === "opened") {
|
|
13497
|
-
* console.log("Signal opened:", result.signal);
|
|
13498
|
-
* } else if (result.action === "closed") {
|
|
13499
|
-
* console.log("PNL:", result.pnl.pnlPercentage);
|
|
13500
|
-
* }
|
|
13501
|
-
* }
|
|
13502
|
-
* ```
|
|
13503
|
-
*/
|
|
13504
|
-
class LiveUtils {
|
|
13690
|
+
class BacktestUtils {
|
|
13505
13691
|
constructor() {
|
|
13692
|
+
/**
|
|
13693
|
+
* Memoized function to get or create BacktestInstance for a symbol-strategy pair.
|
|
13694
|
+
* Each symbol-strategy combination gets its own isolated instance.
|
|
13695
|
+
*/
|
|
13696
|
+
this._getInstance = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => new BacktestInstance(symbol, strategyName));
|
|
13697
|
+
/**
|
|
13698
|
+
* Runs backtest for a symbol with context propagation.
|
|
13699
|
+
*
|
|
13700
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
13701
|
+
* @param context - Execution context with strategy, exchange, and frame names
|
|
13702
|
+
* @returns Async generator yielding closed signals with PNL
|
|
13703
|
+
*/
|
|
13704
|
+
this.run = (symbol, context) => {
|
|
13705
|
+
const instance = this._getInstance(symbol, context.strategyName);
|
|
13706
|
+
return instance.run(symbol, context);
|
|
13707
|
+
};
|
|
13708
|
+
/**
|
|
13709
|
+
* Runs backtest in background without yielding results.
|
|
13710
|
+
*
|
|
13711
|
+
* Consumes all backtest results internally without exposing them.
|
|
13712
|
+
* Useful for running backtests for side effects only (callbacks, logging).
|
|
13713
|
+
*
|
|
13714
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
13715
|
+
* @param context - Execution context with strategy, exchange, and frame names
|
|
13716
|
+
* @returns Cancellation closure
|
|
13717
|
+
*
|
|
13718
|
+
* @example
|
|
13719
|
+
* ```typescript
|
|
13720
|
+
* // Run backtest silently, only callbacks will fire
|
|
13721
|
+
* await Backtest.background("BTCUSDT", {
|
|
13722
|
+
* strategyName: "my-strategy",
|
|
13723
|
+
* exchangeName: "my-exchange",
|
|
13724
|
+
* frameName: "1d-backtest"
|
|
13725
|
+
* });
|
|
13726
|
+
* console.log("Backtest completed");
|
|
13727
|
+
* ```
|
|
13728
|
+
*/
|
|
13729
|
+
this.background = (symbol, context) => {
|
|
13730
|
+
const instance = this._getInstance(symbol, context.strategyName);
|
|
13731
|
+
return instance.background(symbol, context);
|
|
13732
|
+
};
|
|
13733
|
+
/**
|
|
13734
|
+
* Stops the strategy from generating new signals.
|
|
13735
|
+
*
|
|
13736
|
+
* Sets internal flag to prevent strategy from opening new signals.
|
|
13737
|
+
* Current active signal (if any) will complete normally.
|
|
13738
|
+
* Backtest will stop at the next safe point (idle state or after signal closes).
|
|
13739
|
+
*
|
|
13740
|
+
* @param symbol - Trading pair symbol
|
|
13741
|
+
* @param strategyName - Strategy name to stop
|
|
13742
|
+
* @returns Promise that resolves when stop flag is set
|
|
13743
|
+
*
|
|
13744
|
+
* @example
|
|
13745
|
+
* ```typescript
|
|
13746
|
+
* // Stop strategy after some condition
|
|
13747
|
+
* await Backtest.stop("BTCUSDT", "my-strategy");
|
|
13748
|
+
* ```
|
|
13749
|
+
*/
|
|
13750
|
+
this.stop = async (symbol, strategyName) => {
|
|
13751
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
13752
|
+
return await instance.stop(symbol, strategyName);
|
|
13753
|
+
};
|
|
13754
|
+
/**
|
|
13755
|
+
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
13756
|
+
*
|
|
13757
|
+
* @param symbol - Trading pair symbol
|
|
13758
|
+
* @param strategyName - Strategy name to get data for
|
|
13759
|
+
* @returns Promise resolving to statistical data object
|
|
13760
|
+
*
|
|
13761
|
+
* @example
|
|
13762
|
+
* ```typescript
|
|
13763
|
+
* const stats = await Backtest.getData("BTCUSDT", "my-strategy");
|
|
13764
|
+
* console.log(stats.sharpeRatio, stats.winRate);
|
|
13765
|
+
* ```
|
|
13766
|
+
*/
|
|
13767
|
+
this.getData = async (symbol, strategyName) => {
|
|
13768
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
13769
|
+
return await instance.getData(symbol, strategyName);
|
|
13770
|
+
};
|
|
13771
|
+
/**
|
|
13772
|
+
* Generates markdown report with all closed signals for a symbol-strategy pair.
|
|
13773
|
+
*
|
|
13774
|
+
* @param symbol - Trading pair symbol
|
|
13775
|
+
* @param strategyName - Strategy name to generate report for
|
|
13776
|
+
* @returns Promise resolving to markdown formatted report string
|
|
13777
|
+
*
|
|
13778
|
+
* @example
|
|
13779
|
+
* ```typescript
|
|
13780
|
+
* const markdown = await Backtest.getReport("BTCUSDT", "my-strategy");
|
|
13781
|
+
* console.log(markdown);
|
|
13782
|
+
* ```
|
|
13783
|
+
*/
|
|
13784
|
+
this.getReport = async (symbol, strategyName) => {
|
|
13785
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
13786
|
+
return await instance.getReport(symbol, strategyName);
|
|
13787
|
+
};
|
|
13788
|
+
/**
|
|
13789
|
+
* Saves strategy report to disk.
|
|
13790
|
+
*
|
|
13791
|
+
* @param symbol - Trading pair symbol
|
|
13792
|
+
* @param strategyName - Strategy name to save report for
|
|
13793
|
+
* @param path - Optional directory path to save report (default: "./dump/backtest")
|
|
13794
|
+
*
|
|
13795
|
+
* @example
|
|
13796
|
+
* ```typescript
|
|
13797
|
+
* // Save to default path: ./dump/backtest/my-strategy.md
|
|
13798
|
+
* await Backtest.dump("BTCUSDT", "my-strategy");
|
|
13799
|
+
*
|
|
13800
|
+
* // Save to custom path: ./custom/path/my-strategy.md
|
|
13801
|
+
* await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
13802
|
+
* ```
|
|
13803
|
+
*/
|
|
13804
|
+
this.dump = async (symbol, strategyName, path) => {
|
|
13805
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
13806
|
+
return await instance.dump(symbol, strategyName, path);
|
|
13807
|
+
};
|
|
13808
|
+
/**
|
|
13809
|
+
* Lists all active backtest instances with their current status.
|
|
13810
|
+
*
|
|
13811
|
+
* @returns Promise resolving to array of status objects for all instances
|
|
13812
|
+
*
|
|
13813
|
+
* @example
|
|
13814
|
+
* ```typescript
|
|
13815
|
+
* const statusList = await Backtest.list();
|
|
13816
|
+
* statusList.forEach(status => {
|
|
13817
|
+
* console.log(`${status.symbol} - ${status.strategyName}: ${status.status}`);
|
|
13818
|
+
* });
|
|
13819
|
+
* ```
|
|
13820
|
+
*/
|
|
13821
|
+
this.list = async () => {
|
|
13822
|
+
const instanceList = this._getInstance.values();
|
|
13823
|
+
return await Promise.all(instanceList.map((instance) => instance.getStatus()));
|
|
13824
|
+
};
|
|
13825
|
+
}
|
|
13826
|
+
}
|
|
13827
|
+
/**
|
|
13828
|
+
* Singleton instance of BacktestUtils for convenient backtest operations.
|
|
13829
|
+
*
|
|
13830
|
+
* @example
|
|
13831
|
+
* ```typescript
|
|
13832
|
+
* import { Backtest } from "./classes/Backtest";
|
|
13833
|
+
*
|
|
13834
|
+
* for await (const result of Backtest.run("BTCUSDT", {
|
|
13835
|
+
* strategyName: "my-strategy",
|
|
13836
|
+
* exchangeName: "my-exchange",
|
|
13837
|
+
* frameName: "1d-backtest"
|
|
13838
|
+
* })) {
|
|
13839
|
+
* if (result.action === "closed") {
|
|
13840
|
+
* console.log("PNL:", result.pnl.pnlPercentage);
|
|
13841
|
+
* }
|
|
13842
|
+
* }
|
|
13843
|
+
* ```
|
|
13844
|
+
*/
|
|
13845
|
+
const Backtest = new BacktestUtils();
|
|
13846
|
+
|
|
13847
|
+
const LIVE_METHOD_NAME_RUN = "LiveUtils.run";
|
|
13848
|
+
const LIVE_METHOD_NAME_BACKGROUND = "LiveUtils.background";
|
|
13849
|
+
const LIVE_METHOD_NAME_STOP = "LiveUtils.stop";
|
|
13850
|
+
const LIVE_METHOD_NAME_GET_REPORT = "LiveUtils.getReport";
|
|
13851
|
+
const LIVE_METHOD_NAME_DUMP = "LiveUtils.dump";
|
|
13852
|
+
const LIVE_METHOD_NAME_TASK = "LiveUtils.task";
|
|
13853
|
+
const LIVE_METHOD_NAME_GET_STATUS = "LiveUtils.getStatus";
|
|
13854
|
+
/**
|
|
13855
|
+
* Internal task function that runs live trading and handles completion.
|
|
13856
|
+
* Consumes live trading results and updates instance state flags.
|
|
13857
|
+
*
|
|
13858
|
+
* @param symbol - Trading pair symbol
|
|
13859
|
+
* @param context - Execution context with strategy and exchange names
|
|
13860
|
+
* @param self - LiveInstance reference for state management
|
|
13861
|
+
* @returns Promise that resolves when live trading completes
|
|
13862
|
+
*
|
|
13863
|
+
* @internal
|
|
13864
|
+
*/
|
|
13865
|
+
const INSTANCE_TASK_FN$1 = async (symbol, context, self) => {
|
|
13866
|
+
{
|
|
13867
|
+
self._isStopped = false;
|
|
13868
|
+
self._isDone = false;
|
|
13869
|
+
}
|
|
13870
|
+
for await (const signal of self.run(symbol, context)) {
|
|
13871
|
+
if (signal?.action === "closed" && self._isStopped) {
|
|
13872
|
+
break;
|
|
13873
|
+
}
|
|
13874
|
+
}
|
|
13875
|
+
if (!self._isDone) {
|
|
13876
|
+
await doneLiveSubject.next({
|
|
13877
|
+
exchangeName: context.exchangeName,
|
|
13878
|
+
strategyName: context.strategyName,
|
|
13879
|
+
backtest: false,
|
|
13880
|
+
symbol,
|
|
13881
|
+
});
|
|
13882
|
+
}
|
|
13883
|
+
self._isDone = true;
|
|
13884
|
+
};
|
|
13885
|
+
/**
|
|
13886
|
+
* Instance class for live trading operations on a specific symbol-strategy pair.
|
|
13887
|
+
*
|
|
13888
|
+
* Provides isolated live trading execution and reporting for a single symbol-strategy combination.
|
|
13889
|
+
* Each instance maintains its own state and context.
|
|
13890
|
+
*
|
|
13891
|
+
* @example
|
|
13892
|
+
* ```typescript
|
|
13893
|
+
* const instance = new LiveInstance("BTCUSDT", "my-strategy");
|
|
13894
|
+
*
|
|
13895
|
+
* for await (const result of instance.run("BTCUSDT", {
|
|
13896
|
+
* strategyName: "my-strategy",
|
|
13897
|
+
* exchangeName: "my-exchange"
|
|
13898
|
+
* })) {
|
|
13899
|
+
* if (result.action === "closed") {
|
|
13900
|
+
* console.log("Signal closed, PNL:", result.pnl.pnlPercentage);
|
|
13901
|
+
* }
|
|
13902
|
+
* }
|
|
13903
|
+
* ```
|
|
13904
|
+
*/
|
|
13905
|
+
class LiveInstance {
|
|
13906
|
+
/**
|
|
13907
|
+
* Creates a new LiveInstance for a specific symbol-strategy pair.
|
|
13908
|
+
*
|
|
13909
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
13910
|
+
* @param strategyName - Strategy name for this live trading instance
|
|
13911
|
+
*/
|
|
13912
|
+
constructor(symbol, strategyName) {
|
|
13913
|
+
this.symbol = symbol;
|
|
13914
|
+
this.strategyName = strategyName;
|
|
13915
|
+
/** Internal flag indicating if live trading was stopped manually */
|
|
13916
|
+
this._isStopped = false;
|
|
13917
|
+
/** Internal flag indicating if live trading task completed */
|
|
13918
|
+
this._isDone = false;
|
|
13919
|
+
/**
|
|
13920
|
+
* Internal singlerun task that executes the live trading.
|
|
13921
|
+
* Ensures only one live trading run per instance using singlerun wrapper.
|
|
13922
|
+
*
|
|
13923
|
+
* @param symbol - Trading pair symbol
|
|
13924
|
+
* @param context - Execution context with strategy and exchange names
|
|
13925
|
+
* @returns Promise that resolves when live trading completes
|
|
13926
|
+
*
|
|
13927
|
+
* @internal
|
|
13928
|
+
*/
|
|
13929
|
+
this.task = singlerun(async (symbol, context) => {
|
|
13930
|
+
backtest$1.loggerService.info(LIVE_METHOD_NAME_TASK, {
|
|
13931
|
+
symbol,
|
|
13932
|
+
context,
|
|
13933
|
+
});
|
|
13934
|
+
return await INSTANCE_TASK_FN$1(symbol, context, this);
|
|
13935
|
+
});
|
|
13936
|
+
/**
|
|
13937
|
+
* Gets the current status of this live trading instance.
|
|
13938
|
+
*
|
|
13939
|
+
* @returns Promise resolving to status object with symbol, strategyName, and task status
|
|
13940
|
+
*
|
|
13941
|
+
* @example
|
|
13942
|
+
* ```typescript
|
|
13943
|
+
* const instance = new LiveInstance("BTCUSDT", "my-strategy");
|
|
13944
|
+
* const status = await instance.getStatus();
|
|
13945
|
+
* console.log(status.status); // "idle", "running", or "done"
|
|
13946
|
+
* ```
|
|
13947
|
+
*/
|
|
13948
|
+
this.getStatus = async () => {
|
|
13949
|
+
backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_STATUS);
|
|
13950
|
+
return {
|
|
13951
|
+
symbol: this.symbol,
|
|
13952
|
+
strategyName: this.strategyName,
|
|
13953
|
+
status: this.task.getStatus(),
|
|
13954
|
+
};
|
|
13955
|
+
};
|
|
13506
13956
|
/**
|
|
13507
13957
|
* Runs live trading for a symbol with context propagation.
|
|
13508
13958
|
*
|
|
@@ -13544,9 +13994,8 @@ class LiveUtils {
|
|
|
13544
13994
|
*
|
|
13545
13995
|
* @example
|
|
13546
13996
|
* ```typescript
|
|
13547
|
-
*
|
|
13548
|
-
*
|
|
13549
|
-
* await Live.background("BTCUSDT", {
|
|
13997
|
+
* const instance = new LiveInstance();
|
|
13998
|
+
* const cancel = instance.background("BTCUSDT", {
|
|
13550
13999
|
* strategyName: "my-strategy",
|
|
13551
14000
|
* exchangeName: "my-exchange"
|
|
13552
14001
|
* });
|
|
@@ -13557,34 +14006,16 @@ class LiveUtils {
|
|
|
13557
14006
|
symbol,
|
|
13558
14007
|
context,
|
|
13559
14008
|
});
|
|
13560
|
-
|
|
13561
|
-
let isDone = false;
|
|
13562
|
-
const task = async () => {
|
|
13563
|
-
for await (const signal of this.run(symbol, context)) {
|
|
13564
|
-
if (signal?.action === "closed" && isStopped) {
|
|
13565
|
-
break;
|
|
13566
|
-
}
|
|
13567
|
-
}
|
|
13568
|
-
if (!isDone) {
|
|
13569
|
-
await doneLiveSubject.next({
|
|
13570
|
-
exchangeName: context.exchangeName,
|
|
13571
|
-
strategyName: context.strategyName,
|
|
13572
|
-
backtest: false,
|
|
13573
|
-
symbol,
|
|
13574
|
-
});
|
|
13575
|
-
}
|
|
13576
|
-
isDone = true;
|
|
13577
|
-
};
|
|
13578
|
-
task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
14009
|
+
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
13579
14010
|
return () => {
|
|
13580
|
-
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
14011
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName }, false);
|
|
13581
14012
|
backtest$1.strategyGlobalService
|
|
13582
14013
|
.getPendingSignal(symbol, context.strategyName)
|
|
13583
14014
|
.then(async (pendingSignal) => {
|
|
13584
14015
|
if (pendingSignal) {
|
|
13585
14016
|
return;
|
|
13586
14017
|
}
|
|
13587
|
-
if (!
|
|
14018
|
+
if (!this._isDone) {
|
|
13588
14019
|
await doneLiveSubject.next({
|
|
13589
14020
|
exchangeName: context.exchangeName,
|
|
13590
14021
|
strategyName: context.strategyName,
|
|
@@ -13592,11 +14023,200 @@ class LiveUtils {
|
|
|
13592
14023
|
symbol,
|
|
13593
14024
|
});
|
|
13594
14025
|
}
|
|
13595
|
-
|
|
14026
|
+
this._isDone = true;
|
|
13596
14027
|
});
|
|
13597
|
-
|
|
14028
|
+
this._isStopped = true;
|
|
13598
14029
|
};
|
|
13599
14030
|
};
|
|
14031
|
+
/**
|
|
14032
|
+
* Stops the strategy from generating new signals.
|
|
14033
|
+
*
|
|
14034
|
+
* Sets internal flag to prevent strategy from opening new signals.
|
|
14035
|
+
* Current active signal (if any) will complete normally.
|
|
14036
|
+
* Live trading will stop at the next safe point (idle/closed state).
|
|
14037
|
+
*
|
|
14038
|
+
* @param symbol - Trading pair symbol
|
|
14039
|
+
* @param strategyName - Strategy name to stop
|
|
14040
|
+
* @returns Promise that resolves when stop flag is set
|
|
14041
|
+
*
|
|
14042
|
+
* @example
|
|
14043
|
+
* ```typescript
|
|
14044
|
+
* const instance = new LiveInstance();
|
|
14045
|
+
* await instance.stop("BTCUSDT", "my-strategy");
|
|
14046
|
+
* ```
|
|
14047
|
+
*/
|
|
14048
|
+
this.stop = async (symbol, strategyName) => {
|
|
14049
|
+
backtest$1.loggerService.info(LIVE_METHOD_NAME_STOP, {
|
|
14050
|
+
symbol,
|
|
14051
|
+
strategyName,
|
|
14052
|
+
});
|
|
14053
|
+
await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, false);
|
|
14054
|
+
};
|
|
14055
|
+
/**
|
|
14056
|
+
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
14057
|
+
*
|
|
14058
|
+
* @param symbol - Trading pair symbol
|
|
14059
|
+
* @param strategyName - Strategy name to get data for
|
|
14060
|
+
* @returns Promise resolving to statistical data object
|
|
14061
|
+
*
|
|
14062
|
+
* @example
|
|
14063
|
+
* ```typescript
|
|
14064
|
+
* const instance = new LiveInstance();
|
|
14065
|
+
* const stats = await instance.getData("BTCUSDT", "my-strategy");
|
|
14066
|
+
* console.log(stats.sharpeRatio, stats.winRate);
|
|
14067
|
+
* ```
|
|
14068
|
+
*/
|
|
14069
|
+
this.getData = async (symbol, strategyName) => {
|
|
14070
|
+
backtest$1.loggerService.info("LiveUtils.getData", {
|
|
14071
|
+
symbol,
|
|
14072
|
+
strategyName,
|
|
14073
|
+
});
|
|
14074
|
+
return await backtest$1.liveMarkdownService.getData(symbol, strategyName);
|
|
14075
|
+
};
|
|
14076
|
+
/**
|
|
14077
|
+
* Generates markdown report with all events for a symbol-strategy pair.
|
|
14078
|
+
*
|
|
14079
|
+
* @param symbol - Trading pair symbol
|
|
14080
|
+
* @param strategyName - Strategy name to generate report for
|
|
14081
|
+
* @returns Promise resolving to markdown formatted report string
|
|
14082
|
+
*
|
|
14083
|
+
* @example
|
|
14084
|
+
* ```typescript
|
|
14085
|
+
* const instance = new LiveInstance();
|
|
14086
|
+
* const markdown = await instance.getReport("BTCUSDT", "my-strategy");
|
|
14087
|
+
* console.log(markdown);
|
|
14088
|
+
* ```
|
|
14089
|
+
*/
|
|
14090
|
+
this.getReport = async (symbol, strategyName) => {
|
|
14091
|
+
backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
|
|
14092
|
+
symbol,
|
|
14093
|
+
strategyName,
|
|
14094
|
+
});
|
|
14095
|
+
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
|
|
14096
|
+
};
|
|
14097
|
+
/**
|
|
14098
|
+
* Saves strategy report to disk.
|
|
14099
|
+
*
|
|
14100
|
+
* @param symbol - Trading pair symbol
|
|
14101
|
+
* @param strategyName - Strategy name to save report for
|
|
14102
|
+
* @param path - Optional directory path to save report (default: "./dump/live")
|
|
14103
|
+
*
|
|
14104
|
+
* @example
|
|
14105
|
+
* ```typescript
|
|
14106
|
+
* const instance = new LiveInstance();
|
|
14107
|
+
* // Save to default path: ./dump/live/my-strategy.md
|
|
14108
|
+
* await instance.dump("BTCUSDT", "my-strategy");
|
|
14109
|
+
*
|
|
14110
|
+
* // Save to custom path: ./custom/path/my-strategy.md
|
|
14111
|
+
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
14112
|
+
* ```
|
|
14113
|
+
*/
|
|
14114
|
+
this.dump = async (symbol, strategyName, path) => {
|
|
14115
|
+
backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
|
|
14116
|
+
symbol,
|
|
14117
|
+
strategyName,
|
|
14118
|
+
path,
|
|
14119
|
+
});
|
|
14120
|
+
await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
|
|
14121
|
+
};
|
|
14122
|
+
}
|
|
14123
|
+
}
|
|
14124
|
+
/**
|
|
14125
|
+
* Utility class for live trading operations.
|
|
14126
|
+
*
|
|
14127
|
+
* Provides simplified access to liveCommandService.run() with logging.
|
|
14128
|
+
* Exported as singleton instance for convenient usage.
|
|
14129
|
+
*
|
|
14130
|
+
* Features:
|
|
14131
|
+
* - Infinite async generator (never completes)
|
|
14132
|
+
* - Crash recovery via persisted state
|
|
14133
|
+
* - Real-time progression with Date.now()
|
|
14134
|
+
*
|
|
14135
|
+
* @example
|
|
14136
|
+
* ```typescript
|
|
14137
|
+
* import { Live } from "./classes/Live";
|
|
14138
|
+
*
|
|
14139
|
+
* // Infinite loop - use Ctrl+C to stop
|
|
14140
|
+
* for await (const result of Live.run("BTCUSDT", {
|
|
14141
|
+
* strategyName: "my-strategy",
|
|
14142
|
+
* exchangeName: "my-exchange",
|
|
14143
|
+
* frameName: ""
|
|
14144
|
+
* })) {
|
|
14145
|
+
* if (result.action === "opened") {
|
|
14146
|
+
* console.log("Signal opened:", result.signal);
|
|
14147
|
+
* } else if (result.action === "closed") {
|
|
14148
|
+
* console.log("PNL:", result.pnl.pnlPercentage);
|
|
14149
|
+
* }
|
|
14150
|
+
* }
|
|
14151
|
+
* ```
|
|
14152
|
+
*/
|
|
14153
|
+
class LiveUtils {
|
|
14154
|
+
constructor() {
|
|
14155
|
+
/**
|
|
14156
|
+
* Memoized function to get or create LiveInstance for a symbol-strategy pair.
|
|
14157
|
+
* Each symbol-strategy combination gets its own isolated instance.
|
|
14158
|
+
*/
|
|
14159
|
+
this._getInstance = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => new LiveInstance(symbol, strategyName));
|
|
14160
|
+
/**
|
|
14161
|
+
* Runs live trading for a symbol with context propagation.
|
|
14162
|
+
*
|
|
14163
|
+
* Infinite async generator with crash recovery support.
|
|
14164
|
+
* Process can crash and restart - state will be recovered from disk.
|
|
14165
|
+
*
|
|
14166
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
14167
|
+
* @param context - Execution context with strategy and exchange names
|
|
14168
|
+
* @returns Infinite async generator yielding opened and closed signals
|
|
14169
|
+
*/
|
|
14170
|
+
this.run = (symbol, context) => {
|
|
14171
|
+
const instance = this._getInstance(symbol, context.strategyName);
|
|
14172
|
+
return instance.run(symbol, context);
|
|
14173
|
+
};
|
|
14174
|
+
/**
|
|
14175
|
+
* Runs live trading in background without yielding results.
|
|
14176
|
+
*
|
|
14177
|
+
* Consumes all live trading results internally without exposing them.
|
|
14178
|
+
* Infinite loop - will run until process is stopped or crashes.
|
|
14179
|
+
* Useful for running live trading for side effects only (callbacks, persistence).
|
|
14180
|
+
*
|
|
14181
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
14182
|
+
* @param context - Execution context with strategy and exchange names
|
|
14183
|
+
* @returns Cancellation closure
|
|
14184
|
+
*
|
|
14185
|
+
* @example
|
|
14186
|
+
* ```typescript
|
|
14187
|
+
* // Run live trading silently in background, only callbacks will fire
|
|
14188
|
+
* // This will run forever until Ctrl+C
|
|
14189
|
+
* await Live.background("BTCUSDT", {
|
|
14190
|
+
* strategyName: "my-strategy",
|
|
14191
|
+
* exchangeName: "my-exchange"
|
|
14192
|
+
* });
|
|
14193
|
+
* ```
|
|
14194
|
+
*/
|
|
14195
|
+
this.background = (symbol, context) => {
|
|
14196
|
+
const instance = this._getInstance(symbol, context.strategyName);
|
|
14197
|
+
return instance.background(symbol, context);
|
|
14198
|
+
};
|
|
14199
|
+
/**
|
|
14200
|
+
* Stops the strategy from generating new signals.
|
|
14201
|
+
*
|
|
14202
|
+
* Sets internal flag to prevent strategy from opening new signals.
|
|
14203
|
+
* Current active signal (if any) will complete normally.
|
|
14204
|
+
* Live trading will stop at the next safe point (idle/closed state).
|
|
14205
|
+
*
|
|
14206
|
+
* @param symbol - Trading pair symbol
|
|
14207
|
+
* @param strategyName - Strategy name to stop
|
|
14208
|
+
* @returns Promise that resolves when stop flag is set
|
|
14209
|
+
*
|
|
14210
|
+
* @example
|
|
14211
|
+
* ```typescript
|
|
14212
|
+
* // Stop live trading gracefully
|
|
14213
|
+
* await Live.stop("BTCUSDT", "my-strategy");
|
|
14214
|
+
* ```
|
|
14215
|
+
*/
|
|
14216
|
+
this.stop = async (symbol, strategyName) => {
|
|
14217
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
14218
|
+
return await instance.stop(symbol, strategyName);
|
|
14219
|
+
};
|
|
13600
14220
|
/**
|
|
13601
14221
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
13602
14222
|
*
|
|
@@ -13611,11 +14231,8 @@ class LiveUtils {
|
|
|
13611
14231
|
* ```
|
|
13612
14232
|
*/
|
|
13613
14233
|
this.getData = async (symbol, strategyName) => {
|
|
13614
|
-
|
|
13615
|
-
|
|
13616
|
-
strategyName,
|
|
13617
|
-
});
|
|
13618
|
-
return await backtest$1.liveMarkdownService.getData(symbol, strategyName);
|
|
14234
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
14235
|
+
return await instance.getData(symbol, strategyName);
|
|
13619
14236
|
};
|
|
13620
14237
|
/**
|
|
13621
14238
|
* Generates markdown report with all events for a symbol-strategy pair.
|
|
@@ -13631,11 +14248,8 @@ class LiveUtils {
|
|
|
13631
14248
|
* ```
|
|
13632
14249
|
*/
|
|
13633
14250
|
this.getReport = async (symbol, strategyName) => {
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
strategyName,
|
|
13637
|
-
});
|
|
13638
|
-
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
|
|
14251
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
14252
|
+
return await instance.getReport(symbol, strategyName);
|
|
13639
14253
|
};
|
|
13640
14254
|
/**
|
|
13641
14255
|
* Saves strategy report to disk.
|
|
@@ -13654,12 +14268,25 @@ class LiveUtils {
|
|
|
13654
14268
|
* ```
|
|
13655
14269
|
*/
|
|
13656
14270
|
this.dump = async (symbol, strategyName, path) => {
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
14271
|
+
const instance = this._getInstance(symbol, strategyName);
|
|
14272
|
+
return await instance.dump(symbol, strategyName, path);
|
|
14273
|
+
};
|
|
14274
|
+
/**
|
|
14275
|
+
* Lists all active live trading instances with their current status.
|
|
14276
|
+
*
|
|
14277
|
+
* @returns Promise resolving to array of status objects for all instances
|
|
14278
|
+
*
|
|
14279
|
+
* @example
|
|
14280
|
+
* ```typescript
|
|
14281
|
+
* const statusList = await Live.list();
|
|
14282
|
+
* statusList.forEach(status => {
|
|
14283
|
+
* console.log(`${status.symbol} - ${status.strategyName}: ${status.status}`);
|
|
14284
|
+
* });
|
|
14285
|
+
* ```
|
|
14286
|
+
*/
|
|
14287
|
+
this.list = async () => {
|
|
14288
|
+
const instanceList = this._getInstance.values();
|
|
14289
|
+
return await Promise.all(instanceList.map((instance) => instance.getStatus()));
|
|
13663
14290
|
};
|
|
13664
14291
|
}
|
|
13665
14292
|
}
|
|
@@ -13904,30 +14531,112 @@ class Performance {
|
|
|
13904
14531
|
|
|
13905
14532
|
const WALKER_METHOD_NAME_RUN = "WalkerUtils.run";
|
|
13906
14533
|
const WALKER_METHOD_NAME_BACKGROUND = "WalkerUtils.background";
|
|
14534
|
+
const WALKER_METHOD_NAME_STOP = "WalkerUtils.stop";
|
|
13907
14535
|
const WALKER_METHOD_NAME_GET_DATA = "WalkerUtils.getData";
|
|
13908
14536
|
const WALKER_METHOD_NAME_GET_REPORT = "WalkerUtils.getReport";
|
|
13909
14537
|
const WALKER_METHOD_NAME_DUMP = "WalkerUtils.dump";
|
|
14538
|
+
const WALKER_METHOD_NAME_TASK = "WalkerUtils.task";
|
|
14539
|
+
const WALKER_METHOD_NAME_GET_STATUS = "WalkerUtils.getStatus";
|
|
13910
14540
|
/**
|
|
13911
|
-
*
|
|
14541
|
+
* Internal task function that runs walker and handles completion.
|
|
14542
|
+
* Consumes walker results and updates instance state flags.
|
|
13912
14543
|
*
|
|
13913
|
-
*
|
|
13914
|
-
*
|
|
13915
|
-
*
|
|
14544
|
+
* @param symbol - Trading pair symbol
|
|
14545
|
+
* @param context - Execution context with walker name
|
|
14546
|
+
* @param self - WalkerInstance reference for state management
|
|
14547
|
+
* @returns Promise that resolves when walker completes
|
|
14548
|
+
*
|
|
14549
|
+
* @internal
|
|
14550
|
+
*/
|
|
14551
|
+
const INSTANCE_TASK_FN = async (symbol, context, self) => {
|
|
14552
|
+
{
|
|
14553
|
+
self._isStopped = false;
|
|
14554
|
+
self._isDone = false;
|
|
14555
|
+
}
|
|
14556
|
+
for await (const _ of self.run(symbol, context)) {
|
|
14557
|
+
if (self._isStopped) {
|
|
14558
|
+
break;
|
|
14559
|
+
}
|
|
14560
|
+
}
|
|
14561
|
+
if (!self._isDone) {
|
|
14562
|
+
const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
|
|
14563
|
+
await doneWalkerSubject.next({
|
|
14564
|
+
exchangeName: walkerSchema.exchangeName,
|
|
14565
|
+
strategyName: context.walkerName,
|
|
14566
|
+
backtest: true,
|
|
14567
|
+
symbol,
|
|
14568
|
+
});
|
|
14569
|
+
}
|
|
14570
|
+
self._isDone = true;
|
|
14571
|
+
};
|
|
14572
|
+
/**
|
|
14573
|
+
* Instance class for walker operations on a specific symbol-walker pair.
|
|
14574
|
+
*
|
|
14575
|
+
* Provides isolated walker execution and reporting for a single symbol-walker combination.
|
|
14576
|
+
* Each instance maintains its own state and context.
|
|
13916
14577
|
*
|
|
13917
14578
|
* @example
|
|
13918
14579
|
* ```typescript
|
|
13919
|
-
*
|
|
14580
|
+
* const instance = new WalkerInstance("BTCUSDT", "my-walker");
|
|
13920
14581
|
*
|
|
13921
|
-
* for await (const result of
|
|
14582
|
+
* for await (const result of instance.run("BTCUSDT", {
|
|
13922
14583
|
* walkerName: "my-walker"
|
|
13923
14584
|
* })) {
|
|
13924
14585
|
* console.log("Progress:", result.strategiesTested, "/", result.totalStrategies);
|
|
13925
|
-
* console.log("Best strategy:", result.bestStrategy, result.bestMetric);
|
|
13926
14586
|
* }
|
|
13927
14587
|
* ```
|
|
13928
14588
|
*/
|
|
13929
|
-
class
|
|
13930
|
-
|
|
14589
|
+
class WalkerInstance {
|
|
14590
|
+
/**
|
|
14591
|
+
* Creates a new WalkerInstance for a specific symbol-walker pair.
|
|
14592
|
+
*
|
|
14593
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
14594
|
+
* @param walkerName - Walker name for this walker instance
|
|
14595
|
+
*/
|
|
14596
|
+
constructor(symbol, walkerName) {
|
|
14597
|
+
this.symbol = symbol;
|
|
14598
|
+
this.walkerName = walkerName;
|
|
14599
|
+
/** Internal flag indicating if walker was stopped manually */
|
|
14600
|
+
this._isStopped = false;
|
|
14601
|
+
/** Internal flag indicating if walker task completed */
|
|
14602
|
+
this._isDone = false;
|
|
14603
|
+
/**
|
|
14604
|
+
* Internal singlerun task that executes the walker.
|
|
14605
|
+
* Ensures only one walker run per instance using singlerun wrapper.
|
|
14606
|
+
*
|
|
14607
|
+
* @param symbol - Trading pair symbol
|
|
14608
|
+
* @param context - Execution context with walker name
|
|
14609
|
+
* @returns Promise that resolves when walker completes
|
|
14610
|
+
*
|
|
14611
|
+
* @internal
|
|
14612
|
+
*/
|
|
14613
|
+
this.task = singlerun(async (symbol, context) => {
|
|
14614
|
+
backtest$1.loggerService.info(WALKER_METHOD_NAME_TASK, {
|
|
14615
|
+
symbol,
|
|
14616
|
+
context,
|
|
14617
|
+
});
|
|
14618
|
+
return await INSTANCE_TASK_FN(symbol, context, this);
|
|
14619
|
+
});
|
|
14620
|
+
/**
|
|
14621
|
+
* Gets the current status of this walker instance.
|
|
14622
|
+
*
|
|
14623
|
+
* @returns Promise resolving to status object with symbol, walkerName, and task status
|
|
14624
|
+
*
|
|
14625
|
+
* @example
|
|
14626
|
+
* ```typescript
|
|
14627
|
+
* const instance = new WalkerInstance("BTCUSDT", "my-walker");
|
|
14628
|
+
* const status = await instance.getStatus();
|
|
14629
|
+
* console.log(status.status); // "idle", "running", or "done"
|
|
14630
|
+
* ```
|
|
14631
|
+
*/
|
|
14632
|
+
this.getStatus = async () => {
|
|
14633
|
+
backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_STATUS);
|
|
14634
|
+
return {
|
|
14635
|
+
symbol: this.symbol,
|
|
14636
|
+
walkerName: this.walkerName,
|
|
14637
|
+
status: this.task.getStatus(),
|
|
14638
|
+
};
|
|
14639
|
+
};
|
|
13931
14640
|
/**
|
|
13932
14641
|
* Runs walker comparison for a symbol with context propagation.
|
|
13933
14642
|
*
|
|
@@ -13980,11 +14689,10 @@ class WalkerUtils {
|
|
|
13980
14689
|
*
|
|
13981
14690
|
* @example
|
|
13982
14691
|
* ```typescript
|
|
13983
|
-
*
|
|
13984
|
-
*
|
|
14692
|
+
* const instance = new WalkerInstance();
|
|
14693
|
+
* const cancel = instance.background("BTCUSDT", {
|
|
13985
14694
|
* walkerName: "my-walker"
|
|
13986
14695
|
* });
|
|
13987
|
-
* console.log("Walker comparison completed");
|
|
13988
14696
|
* ```
|
|
13989
14697
|
*/
|
|
13990
14698
|
this.background = (symbol, context) => {
|
|
@@ -13993,31 +14701,13 @@ class WalkerUtils {
|
|
|
13993
14701
|
context,
|
|
13994
14702
|
});
|
|
13995
14703
|
const walkerSchema = backtest$1.walkerSchemaService.get(context.walkerName);
|
|
13996
|
-
|
|
13997
|
-
let isDone = false;
|
|
13998
|
-
const task = async () => {
|
|
13999
|
-
for await (const _ of this.run(symbol, context)) {
|
|
14000
|
-
if (isStopped) {
|
|
14001
|
-
break;
|
|
14002
|
-
}
|
|
14003
|
-
}
|
|
14004
|
-
if (!isDone) {
|
|
14005
|
-
await doneWalkerSubject.next({
|
|
14006
|
-
exchangeName: walkerSchema.exchangeName,
|
|
14007
|
-
strategyName: context.walkerName,
|
|
14008
|
-
backtest: true,
|
|
14009
|
-
symbol,
|
|
14010
|
-
});
|
|
14011
|
-
}
|
|
14012
|
-
isDone = true;
|
|
14013
|
-
};
|
|
14014
|
-
task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
14704
|
+
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
14015
14705
|
return () => {
|
|
14016
14706
|
for (const strategyName of walkerSchema.strategies) {
|
|
14017
|
-
backtest$1.strategyGlobalService.stop({ symbol, strategyName });
|
|
14018
|
-
walkerStopSubject.next({ symbol, strategyName });
|
|
14707
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
|
|
14708
|
+
walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
|
|
14019
14709
|
}
|
|
14020
|
-
if (!
|
|
14710
|
+
if (!this._isDone) {
|
|
14021
14711
|
doneWalkerSubject.next({
|
|
14022
14712
|
exchangeName: walkerSchema.exchangeName,
|
|
14023
14713
|
strategyName: context.walkerName,
|
|
@@ -14025,10 +14715,44 @@ class WalkerUtils {
|
|
|
14025
14715
|
symbol,
|
|
14026
14716
|
});
|
|
14027
14717
|
}
|
|
14028
|
-
|
|
14029
|
-
|
|
14718
|
+
this._isDone = true;
|
|
14719
|
+
this._isStopped = true;
|
|
14030
14720
|
};
|
|
14031
14721
|
};
|
|
14722
|
+
/**
|
|
14723
|
+
* Stops all strategies in the walker from generating new signals.
|
|
14724
|
+
*
|
|
14725
|
+
* Iterates through all strategies defined in walker schema and:
|
|
14726
|
+
* 1. Sends stop signal via walkerStopSubject (interrupts current running strategy)
|
|
14727
|
+
* 2. Sets internal stop flag for each strategy (prevents new signals)
|
|
14728
|
+
*
|
|
14729
|
+
* Current active signals (if any) will complete normally.
|
|
14730
|
+
* Walker will stop at the next safe point.
|
|
14731
|
+
*
|
|
14732
|
+
* Supports multiple walkers running on the same symbol simultaneously.
|
|
14733
|
+
* Stop signal is filtered by walkerName to prevent interference.
|
|
14734
|
+
*
|
|
14735
|
+
* @param symbol - Trading pair symbol
|
|
14736
|
+
* @param walkerName - Walker name to stop
|
|
14737
|
+
* @returns Promise that resolves when all stop flags are set
|
|
14738
|
+
*
|
|
14739
|
+
* @example
|
|
14740
|
+
* ```typescript
|
|
14741
|
+
* const instance = new WalkerInstance();
|
|
14742
|
+
* await instance.stop("BTCUSDT", "my-walker");
|
|
14743
|
+
* ```
|
|
14744
|
+
*/
|
|
14745
|
+
this.stop = async (symbol, walkerName) => {
|
|
14746
|
+
backtest$1.loggerService.info(WALKER_METHOD_NAME_STOP, {
|
|
14747
|
+
symbol,
|
|
14748
|
+
walkerName,
|
|
14749
|
+
});
|
|
14750
|
+
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
14751
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
14752
|
+
await walkerStopSubject.next({ symbol, strategyName, walkerName });
|
|
14753
|
+
await backtest$1.strategyGlobalService.stop({ symbol, strategyName }, true);
|
|
14754
|
+
}
|
|
14755
|
+
};
|
|
14032
14756
|
/**
|
|
14033
14757
|
* Gets walker results data from all strategy comparisons.
|
|
14034
14758
|
*
|
|
@@ -14038,7 +14762,8 @@ class WalkerUtils {
|
|
|
14038
14762
|
*
|
|
14039
14763
|
* @example
|
|
14040
14764
|
* ```typescript
|
|
14041
|
-
* const
|
|
14765
|
+
* const instance = new WalkerInstance();
|
|
14766
|
+
* const results = await instance.getData("BTCUSDT", "my-walker");
|
|
14042
14767
|
* console.log(results.bestStrategy, results.bestMetric);
|
|
14043
14768
|
* ```
|
|
14044
14769
|
*/
|
|
@@ -14062,7 +14787,8 @@ class WalkerUtils {
|
|
|
14062
14787
|
*
|
|
14063
14788
|
* @example
|
|
14064
14789
|
* ```typescript
|
|
14065
|
-
* const
|
|
14790
|
+
* const instance = new WalkerInstance();
|
|
14791
|
+
* const markdown = await instance.getReport("BTCUSDT", "my-walker");
|
|
14066
14792
|
* console.log(markdown);
|
|
14067
14793
|
* ```
|
|
14068
14794
|
*/
|
|
@@ -14086,11 +14812,12 @@ class WalkerUtils {
|
|
|
14086
14812
|
*
|
|
14087
14813
|
* @example
|
|
14088
14814
|
* ```typescript
|
|
14815
|
+
* const instance = new WalkerInstance();
|
|
14089
14816
|
* // Save to default path: ./dump/walker/my-walker.md
|
|
14090
|
-
* await
|
|
14817
|
+
* await instance.dump("BTCUSDT", "my-walker");
|
|
14091
14818
|
*
|
|
14092
14819
|
* // Save to custom path: ./custom/path/my-walker.md
|
|
14093
|
-
* await
|
|
14820
|
+
* await instance.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
14094
14821
|
* ```
|
|
14095
14822
|
*/
|
|
14096
14823
|
this.dump = async (symbol, walkerName, path) => {
|
|
@@ -14107,6 +14834,166 @@ class WalkerUtils {
|
|
|
14107
14834
|
};
|
|
14108
14835
|
}
|
|
14109
14836
|
}
|
|
14837
|
+
/**
|
|
14838
|
+
* Utility class for walker operations.
|
|
14839
|
+
*
|
|
14840
|
+
* Provides simplified access to walkerCommandService.run() with logging.
|
|
14841
|
+
* Automatically pulls exchangeName and frameName from walker schema.
|
|
14842
|
+
* Exported as singleton instance for convenient usage.
|
|
14843
|
+
*
|
|
14844
|
+
* @example
|
|
14845
|
+
* ```typescript
|
|
14846
|
+
* import { Walker } from "./classes/Walker";
|
|
14847
|
+
*
|
|
14848
|
+
* for await (const result of Walker.run("BTCUSDT", {
|
|
14849
|
+
* walkerName: "my-walker"
|
|
14850
|
+
* })) {
|
|
14851
|
+
* console.log("Progress:", result.strategiesTested, "/", result.totalStrategies);
|
|
14852
|
+
* console.log("Best strategy:", result.bestStrategy, result.bestMetric);
|
|
14853
|
+
* }
|
|
14854
|
+
* ```
|
|
14855
|
+
*/
|
|
14856
|
+
class WalkerUtils {
|
|
14857
|
+
constructor() {
|
|
14858
|
+
/**
|
|
14859
|
+
* Memoized function to get or create WalkerInstance for a symbol-walker pair.
|
|
14860
|
+
* Each symbol-walker combination gets its own isolated instance.
|
|
14861
|
+
*/
|
|
14862
|
+
this._getInstance = memoize(([symbol, walkerName]) => `${symbol}:${walkerName}`, (symbol, walkerName) => new WalkerInstance(symbol, walkerName));
|
|
14863
|
+
/**
|
|
14864
|
+
* Runs walker comparison for a symbol with context propagation.
|
|
14865
|
+
*
|
|
14866
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
14867
|
+
* @param context - Execution context with walker name
|
|
14868
|
+
* @returns Async generator yielding progress updates after each strategy
|
|
14869
|
+
*/
|
|
14870
|
+
this.run = (symbol, context) => {
|
|
14871
|
+
const instance = this._getInstance(symbol, context.walkerName);
|
|
14872
|
+
return instance.run(symbol, context);
|
|
14873
|
+
};
|
|
14874
|
+
/**
|
|
14875
|
+
* Runs walker comparison in background without yielding results.
|
|
14876
|
+
*
|
|
14877
|
+
* Consumes all walker progress updates internally without exposing them.
|
|
14878
|
+
* Useful for running walker comparison for side effects only (callbacks, logging).
|
|
14879
|
+
*
|
|
14880
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
14881
|
+
* @param context - Execution context with walker name
|
|
14882
|
+
* @returns Cancellation closure
|
|
14883
|
+
*
|
|
14884
|
+
* @example
|
|
14885
|
+
* ```typescript
|
|
14886
|
+
* // Run walker silently, only callbacks will fire
|
|
14887
|
+
* await Walker.background("BTCUSDT", {
|
|
14888
|
+
* walkerName: "my-walker"
|
|
14889
|
+
* });
|
|
14890
|
+
* console.log("Walker comparison completed");
|
|
14891
|
+
* ```
|
|
14892
|
+
*/
|
|
14893
|
+
this.background = (symbol, context) => {
|
|
14894
|
+
const instance = this._getInstance(symbol, context.walkerName);
|
|
14895
|
+
return instance.background(symbol, context);
|
|
14896
|
+
};
|
|
14897
|
+
/**
|
|
14898
|
+
* Stops all strategies in the walker from generating new signals.
|
|
14899
|
+
*
|
|
14900
|
+
* Iterates through all strategies defined in walker schema and:
|
|
14901
|
+
* 1. Sends stop signal via walkerStopSubject (interrupts current running strategy)
|
|
14902
|
+
* 2. Sets internal stop flag for each strategy (prevents new signals)
|
|
14903
|
+
*
|
|
14904
|
+
* Current active signals (if any) will complete normally.
|
|
14905
|
+
* Walker will stop at the next safe point.
|
|
14906
|
+
*
|
|
14907
|
+
* Supports multiple walkers running on the same symbol simultaneously.
|
|
14908
|
+
* Stop signal is filtered by walkerName to prevent interference.
|
|
14909
|
+
*
|
|
14910
|
+
* @param symbol - Trading pair symbol
|
|
14911
|
+
* @param walkerName - Walker name to stop
|
|
14912
|
+
* @returns Promise that resolves when all stop flags are set
|
|
14913
|
+
*
|
|
14914
|
+
* @example
|
|
14915
|
+
* ```typescript
|
|
14916
|
+
* // Stop walker and all its strategies
|
|
14917
|
+
* await Walker.stop("BTCUSDT", "my-walker");
|
|
14918
|
+
* ```
|
|
14919
|
+
*/
|
|
14920
|
+
this.stop = async (symbol, walkerName) => {
|
|
14921
|
+
const instance = this._getInstance(symbol, walkerName);
|
|
14922
|
+
return await instance.stop(symbol, walkerName);
|
|
14923
|
+
};
|
|
14924
|
+
/**
|
|
14925
|
+
* Gets walker results data from all strategy comparisons.
|
|
14926
|
+
*
|
|
14927
|
+
* @param symbol - Trading symbol
|
|
14928
|
+
* @param walkerName - Walker name to get data for
|
|
14929
|
+
* @returns Promise resolving to walker results data object
|
|
14930
|
+
*
|
|
14931
|
+
* @example
|
|
14932
|
+
* ```typescript
|
|
14933
|
+
* const results = await Walker.getData("BTCUSDT", "my-walker");
|
|
14934
|
+
* console.log(results.bestStrategy, results.bestMetric);
|
|
14935
|
+
* ```
|
|
14936
|
+
*/
|
|
14937
|
+
this.getData = async (symbol, walkerName) => {
|
|
14938
|
+
const instance = this._getInstance(symbol, walkerName);
|
|
14939
|
+
return await instance.getData(symbol, walkerName);
|
|
14940
|
+
};
|
|
14941
|
+
/**
|
|
14942
|
+
* Generates markdown report with all strategy comparisons for a walker.
|
|
14943
|
+
*
|
|
14944
|
+
* @param symbol - Trading symbol
|
|
14945
|
+
* @param walkerName - Walker name to generate report for
|
|
14946
|
+
* @returns Promise resolving to markdown formatted report string
|
|
14947
|
+
*
|
|
14948
|
+
* @example
|
|
14949
|
+
* ```typescript
|
|
14950
|
+
* const markdown = await Walker.getReport("BTCUSDT", "my-walker");
|
|
14951
|
+
* console.log(markdown);
|
|
14952
|
+
* ```
|
|
14953
|
+
*/
|
|
14954
|
+
this.getReport = async (symbol, walkerName) => {
|
|
14955
|
+
const instance = this._getInstance(symbol, walkerName);
|
|
14956
|
+
return await instance.getReport(symbol, walkerName);
|
|
14957
|
+
};
|
|
14958
|
+
/**
|
|
14959
|
+
* Saves walker report to disk.
|
|
14960
|
+
*
|
|
14961
|
+
* @param symbol - Trading symbol
|
|
14962
|
+
* @param walkerName - Walker name to save report for
|
|
14963
|
+
* @param path - Optional directory path to save report (default: "./dump/walker")
|
|
14964
|
+
*
|
|
14965
|
+
* @example
|
|
14966
|
+
* ```typescript
|
|
14967
|
+
* // Save to default path: ./dump/walker/my-walker.md
|
|
14968
|
+
* await Walker.dump("BTCUSDT", "my-walker");
|
|
14969
|
+
*
|
|
14970
|
+
* // Save to custom path: ./custom/path/my-walker.md
|
|
14971
|
+
* await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
14972
|
+
* ```
|
|
14973
|
+
*/
|
|
14974
|
+
this.dump = async (symbol, walkerName, path) => {
|
|
14975
|
+
const instance = this._getInstance(symbol, walkerName);
|
|
14976
|
+
return await instance.dump(symbol, walkerName, path);
|
|
14977
|
+
};
|
|
14978
|
+
/**
|
|
14979
|
+
* Lists all active walker instances with their current status.
|
|
14980
|
+
*
|
|
14981
|
+
* @returns Promise resolving to array of status objects for all instances
|
|
14982
|
+
*
|
|
14983
|
+
* @example
|
|
14984
|
+
* ```typescript
|
|
14985
|
+
* const statusList = await Walker.list();
|
|
14986
|
+
* statusList.forEach(status => {
|
|
14987
|
+
* console.log(`${status.symbol} - ${status.walkerName}: ${status.status}`);
|
|
14988
|
+
* });
|
|
14989
|
+
* ```
|
|
14990
|
+
*/
|
|
14991
|
+
this.list = async () => {
|
|
14992
|
+
const instanceList = this._getInstance.values();
|
|
14993
|
+
return await Promise.all(instanceList.map((instance) => instance.getStatus()));
|
|
14994
|
+
};
|
|
14995
|
+
}
|
|
14996
|
+
}
|
|
14110
14997
|
/**
|
|
14111
14998
|
* Singleton instance of WalkerUtils for convenient walker operations.
|
|
14112
14999
|
*
|