backtest-kit 1.4.9 → 1.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +205 -15
- package/build/index.mjs +205 -15
- package/package.json +1 -1
- package/types.d.ts +33 -11
package/build/index.cjs
CHANGED
|
@@ -3128,13 +3128,12 @@ class StrategyConnectionService {
|
|
|
3128
3128
|
* @param strategyName - Name of strategy to stop
|
|
3129
3129
|
* @returns Promise that resolves when stop flag is set
|
|
3130
3130
|
*/
|
|
3131
|
-
this.stop = async (
|
|
3131
|
+
this.stop = async (ctx) => {
|
|
3132
3132
|
this.loggerService.log("strategyConnectionService stop", {
|
|
3133
|
-
|
|
3134
|
-
strategyName,
|
|
3133
|
+
ctx
|
|
3135
3134
|
});
|
|
3136
|
-
const strategy = this.getStrategy(symbol, strategyName);
|
|
3137
|
-
await strategy.stop(symbol, strategyName);
|
|
3135
|
+
const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
|
|
3136
|
+
await strategy.stop(ctx.symbol, ctx.strategyName);
|
|
3138
3137
|
};
|
|
3139
3138
|
/**
|
|
3140
3139
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -4065,13 +4064,12 @@ class StrategyGlobalService {
|
|
|
4065
4064
|
* @param strategyName - Name of strategy to stop
|
|
4066
4065
|
* @returns Promise that resolves when stop flag is set
|
|
4067
4066
|
*/
|
|
4068
|
-
this.stop = async (
|
|
4067
|
+
this.stop = async (ctx) => {
|
|
4069
4068
|
this.loggerService.log("strategyGlobalService stop", {
|
|
4070
|
-
|
|
4071
|
-
strategyName,
|
|
4069
|
+
ctx,
|
|
4072
4070
|
});
|
|
4073
|
-
await this.validate(symbol, strategyName);
|
|
4074
|
-
return await this.strategyConnectionService.stop(
|
|
4071
|
+
await this.validate(ctx.symbol, ctx.strategyName);
|
|
4072
|
+
return await this.strategyConnectionService.stop(ctx);
|
|
4075
4073
|
};
|
|
4076
4074
|
/**
|
|
4077
4075
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -5084,7 +5082,7 @@ class LiveLogicPrivateService {
|
|
|
5084
5082
|
await functoolsKit.sleep(TICK_TTL);
|
|
5085
5083
|
continue;
|
|
5086
5084
|
}
|
|
5087
|
-
// Yield opened, closed
|
|
5085
|
+
// Yield opened, closed results
|
|
5088
5086
|
yield result;
|
|
5089
5087
|
await functoolsKit.sleep(TICK_TTL);
|
|
5090
5088
|
}
|
|
@@ -7264,6 +7262,118 @@ function formatMetric(value) {
|
|
|
7264
7262
|
}
|
|
7265
7263
|
return value.toFixed(2);
|
|
7266
7264
|
}
|
|
7265
|
+
/**
|
|
7266
|
+
* Creates strategy comparison columns based on metric name.
|
|
7267
|
+
* Dynamically builds column configuration with metric-specific header.
|
|
7268
|
+
*
|
|
7269
|
+
* @param metric - Metric being optimized
|
|
7270
|
+
* @returns Array of column configurations for strategy comparison table
|
|
7271
|
+
*/
|
|
7272
|
+
function createStrategyColumns(metric) {
|
|
7273
|
+
return [
|
|
7274
|
+
{
|
|
7275
|
+
key: "rank",
|
|
7276
|
+
label: "Rank",
|
|
7277
|
+
format: (data, index) => `${index + 1}`,
|
|
7278
|
+
},
|
|
7279
|
+
{
|
|
7280
|
+
key: "strategy",
|
|
7281
|
+
label: "Strategy",
|
|
7282
|
+
format: (data) => data.strategyName,
|
|
7283
|
+
},
|
|
7284
|
+
{
|
|
7285
|
+
key: "metric",
|
|
7286
|
+
label: metric,
|
|
7287
|
+
format: (data) => formatMetric(data.metricValue),
|
|
7288
|
+
},
|
|
7289
|
+
{
|
|
7290
|
+
key: "totalSignals",
|
|
7291
|
+
label: "Total Signals",
|
|
7292
|
+
format: (data) => `${data.stats.totalSignals}`,
|
|
7293
|
+
},
|
|
7294
|
+
{
|
|
7295
|
+
key: "winRate",
|
|
7296
|
+
label: "Win Rate",
|
|
7297
|
+
format: (data) => data.stats.winRate !== null
|
|
7298
|
+
? `${data.stats.winRate.toFixed(2)}%`
|
|
7299
|
+
: "N/A",
|
|
7300
|
+
},
|
|
7301
|
+
{
|
|
7302
|
+
key: "avgPnl",
|
|
7303
|
+
label: "Avg PNL",
|
|
7304
|
+
format: (data) => data.stats.avgPnl !== null
|
|
7305
|
+
? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
|
|
7306
|
+
: "N/A",
|
|
7307
|
+
},
|
|
7308
|
+
{
|
|
7309
|
+
key: "totalPnl",
|
|
7310
|
+
label: "Total PNL",
|
|
7311
|
+
format: (data) => data.stats.totalPnl !== null
|
|
7312
|
+
? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
|
|
7313
|
+
: "N/A",
|
|
7314
|
+
},
|
|
7315
|
+
{
|
|
7316
|
+
key: "sharpeRatio",
|
|
7317
|
+
label: "Sharpe Ratio",
|
|
7318
|
+
format: (data) => data.stats.sharpeRatio !== null
|
|
7319
|
+
? `${data.stats.sharpeRatio.toFixed(3)}`
|
|
7320
|
+
: "N/A",
|
|
7321
|
+
},
|
|
7322
|
+
{
|
|
7323
|
+
key: "stdDev",
|
|
7324
|
+
label: "Std Dev",
|
|
7325
|
+
format: (data) => data.stats.stdDev !== null
|
|
7326
|
+
? `${data.stats.stdDev.toFixed(3)}%`
|
|
7327
|
+
: "N/A",
|
|
7328
|
+
},
|
|
7329
|
+
];
|
|
7330
|
+
}
|
|
7331
|
+
/**
|
|
7332
|
+
* Column configuration for PNL table.
|
|
7333
|
+
* Defines all columns for displaying closed signals across strategies.
|
|
7334
|
+
*/
|
|
7335
|
+
const pnlColumns = [
|
|
7336
|
+
{
|
|
7337
|
+
key: "strategy",
|
|
7338
|
+
label: "Strategy",
|
|
7339
|
+
format: (data) => data.strategyName,
|
|
7340
|
+
},
|
|
7341
|
+
{
|
|
7342
|
+
key: "signalId",
|
|
7343
|
+
label: "Signal ID",
|
|
7344
|
+
format: (data) => data.signalId,
|
|
7345
|
+
},
|
|
7346
|
+
{
|
|
7347
|
+
key: "symbol",
|
|
7348
|
+
label: "Symbol",
|
|
7349
|
+
format: (data) => data.symbol,
|
|
7350
|
+
},
|
|
7351
|
+
{
|
|
7352
|
+
key: "position",
|
|
7353
|
+
label: "Position",
|
|
7354
|
+
format: (data) => data.position.toUpperCase(),
|
|
7355
|
+
},
|
|
7356
|
+
{
|
|
7357
|
+
key: "pnl",
|
|
7358
|
+
label: "PNL (net)",
|
|
7359
|
+
format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
|
|
7360
|
+
},
|
|
7361
|
+
{
|
|
7362
|
+
key: "closeReason",
|
|
7363
|
+
label: "Close Reason",
|
|
7364
|
+
format: (data) => data.closeReason,
|
|
7365
|
+
},
|
|
7366
|
+
{
|
|
7367
|
+
key: "openTime",
|
|
7368
|
+
label: "Open Time",
|
|
7369
|
+
format: (data) => new Date(data.openTime).toISOString(),
|
|
7370
|
+
},
|
|
7371
|
+
{
|
|
7372
|
+
key: "closeTime",
|
|
7373
|
+
label: "Close Time",
|
|
7374
|
+
format: (data) => new Date(data.closeTime).toISOString(),
|
|
7375
|
+
},
|
|
7376
|
+
];
|
|
7267
7377
|
/**
|
|
7268
7378
|
* Storage class for accumulating walker results.
|
|
7269
7379
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
@@ -7276,9 +7386,12 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7276
7386
|
this._bestStats = null;
|
|
7277
7387
|
this._bestMetric = null;
|
|
7278
7388
|
this._bestStrategy = null;
|
|
7389
|
+
/** All strategy results for comparison table */
|
|
7390
|
+
this._strategyResults = [];
|
|
7279
7391
|
}
|
|
7280
7392
|
/**
|
|
7281
7393
|
* Adds a strategy result to the storage.
|
|
7394
|
+
* Updates best strategy tracking and accumulates result for comparison table.
|
|
7282
7395
|
*
|
|
7283
7396
|
* @param data - Walker contract with strategy result
|
|
7284
7397
|
*/
|
|
@@ -7292,6 +7405,12 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7292
7405
|
if (data.strategyName === data.bestStrategy) {
|
|
7293
7406
|
this._bestStats = data.stats;
|
|
7294
7407
|
}
|
|
7408
|
+
// Add strategy result to comparison list
|
|
7409
|
+
this._strategyResults.push({
|
|
7410
|
+
strategyName: data.strategyName,
|
|
7411
|
+
stats: data.stats,
|
|
7412
|
+
metricValue: data.metricValue,
|
|
7413
|
+
});
|
|
7295
7414
|
}
|
|
7296
7415
|
/**
|
|
7297
7416
|
* Calculates walker results from strategy results.
|
|
@@ -7316,10 +7435,79 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7316
7435
|
bestStrategy: this._bestStrategy,
|
|
7317
7436
|
bestMetric: this._bestMetric,
|
|
7318
7437
|
bestStats: this._bestStats,
|
|
7438
|
+
strategyResults: this._strategyResults,
|
|
7319
7439
|
};
|
|
7320
7440
|
}
|
|
7441
|
+
/**
|
|
7442
|
+
* Generates comparison table for top N strategies (View).
|
|
7443
|
+
* Sorts strategies by metric value and formats as markdown table.
|
|
7444
|
+
*
|
|
7445
|
+
* @param metric - Metric being optimized
|
|
7446
|
+
* @param topN - Number of top strategies to include (default: 10)
|
|
7447
|
+
* @returns Markdown formatted comparison table
|
|
7448
|
+
*/
|
|
7449
|
+
getComparisonTable(metric, topN = 10) {
|
|
7450
|
+
if (this._strategyResults.length === 0) {
|
|
7451
|
+
return "No strategy results available.";
|
|
7452
|
+
}
|
|
7453
|
+
// Sort strategies by metric value (descending)
|
|
7454
|
+
const sortedResults = [...this._strategyResults].sort((a, b) => {
|
|
7455
|
+
const aValue = a.metricValue ?? -Infinity;
|
|
7456
|
+
const bValue = b.metricValue ?? -Infinity;
|
|
7457
|
+
return bValue - aValue;
|
|
7458
|
+
});
|
|
7459
|
+
// Take top N strategies
|
|
7460
|
+
const topStrategies = sortedResults.slice(0, topN);
|
|
7461
|
+
// Get columns configuration
|
|
7462
|
+
const columns = createStrategyColumns(metric);
|
|
7463
|
+
// Build table header
|
|
7464
|
+
const header = columns.map((col) => col.label);
|
|
7465
|
+
const separator = columns.map(() => "---");
|
|
7466
|
+
// Build table rows
|
|
7467
|
+
const rows = topStrategies.map((result, index) => columns.map((col) => col.format(result, index)));
|
|
7468
|
+
const tableData = [header, separator, ...rows];
|
|
7469
|
+
return functoolsKit.str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
|
|
7470
|
+
}
|
|
7471
|
+
/**
|
|
7472
|
+
* Generates PNL table showing all closed signals across all strategies (View).
|
|
7473
|
+
* Collects all signals from all strategies and formats as markdown table.
|
|
7474
|
+
*
|
|
7475
|
+
* @returns Markdown formatted PNL table
|
|
7476
|
+
*/
|
|
7477
|
+
getPnlTable() {
|
|
7478
|
+
if (this._strategyResults.length === 0) {
|
|
7479
|
+
return "No strategy results available.";
|
|
7480
|
+
}
|
|
7481
|
+
// Collect all closed signals from all strategies
|
|
7482
|
+
const allSignals = [];
|
|
7483
|
+
for (const result of this._strategyResults) {
|
|
7484
|
+
for (const signal of result.stats.signalList) {
|
|
7485
|
+
allSignals.push({
|
|
7486
|
+
strategyName: result.strategyName,
|
|
7487
|
+
signalId: signal.signal.id,
|
|
7488
|
+
symbol: signal.signal.symbol,
|
|
7489
|
+
position: signal.signal.position,
|
|
7490
|
+
pnl: signal.pnl.pnlPercentage,
|
|
7491
|
+
closeReason: signal.closeReason,
|
|
7492
|
+
openTime: signal.signal.pendingAt,
|
|
7493
|
+
closeTime: signal.closeTimestamp,
|
|
7494
|
+
});
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
if (allSignals.length === 0) {
|
|
7498
|
+
return "No closed signals available.";
|
|
7499
|
+
}
|
|
7500
|
+
// Build table header
|
|
7501
|
+
const header = pnlColumns.map((col) => col.label);
|
|
7502
|
+
const separator = pnlColumns.map(() => "---");
|
|
7503
|
+
// Build table rows
|
|
7504
|
+
const rows = allSignals.map((signal) => pnlColumns.map((col) => col.format(signal)));
|
|
7505
|
+
const tableData = [header, separator, ...rows];
|
|
7506
|
+
return functoolsKit.str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
|
|
7507
|
+
}
|
|
7321
7508
|
/**
|
|
7322
7509
|
* Generates markdown report with all strategy results (View).
|
|
7510
|
+
* Includes best strategy summary, comparison table, and PNL table.
|
|
7323
7511
|
*
|
|
7324
7512
|
* @param symbol - Trading symbol
|
|
7325
7513
|
* @param metric - Metric being optimized
|
|
@@ -7328,7 +7516,9 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7328
7516
|
*/
|
|
7329
7517
|
async getReport(symbol, metric, context) {
|
|
7330
7518
|
const results = await this.getData(symbol, metric, context);
|
|
7331
|
-
|
|
7519
|
+
// Get total signals for best strategy
|
|
7520
|
+
const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
|
|
7521
|
+
return functoolsKit.str.newline(`# Walker Comparison Report: ${results.walkerName}`, "", `**Symbol:** ${results.symbol}`, `**Exchange:** ${results.exchangeName}`, `**Frame:** ${results.frameName}`, `**Optimization Metric:** ${results.metric}`, `**Strategies Tested:** ${results.totalStrategies}`, "", `## Best Strategy: ${results.bestStrategy}`, "", `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`, `**Total Signals:** ${bestStrategySignals}`, "", "## Top Strategies Comparison", "", this.getComparisonTable(metric, 10), "", "## All Signals (PNL Table)", "", this.getPnlTable(), "", "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better).");
|
|
7332
7522
|
}
|
|
7333
7523
|
/**
|
|
7334
7524
|
* Saves walker report to disk.
|
|
@@ -12606,7 +12796,7 @@ class BacktestUtils {
|
|
|
12606
12796
|
};
|
|
12607
12797
|
task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
12608
12798
|
return () => {
|
|
12609
|
-
backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
|
|
12799
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
12610
12800
|
backtest$1.strategyGlobalService
|
|
12611
12801
|
.getPendingSignal(symbol, context.strategyName)
|
|
12612
12802
|
.then(async (pendingSignal) => {
|
|
@@ -12819,7 +13009,7 @@ class LiveUtils {
|
|
|
12819
13009
|
};
|
|
12820
13010
|
task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
12821
13011
|
return () => {
|
|
12822
|
-
backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
|
|
13012
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
12823
13013
|
backtest$1.strategyGlobalService
|
|
12824
13014
|
.getPendingSignal(symbol, context.strategyName)
|
|
12825
13015
|
.then(async (pendingSignal) => {
|
|
@@ -13251,7 +13441,7 @@ class WalkerUtils {
|
|
|
13251
13441
|
task().catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
13252
13442
|
return () => {
|
|
13253
13443
|
for (const strategyName of walkerSchema.strategies) {
|
|
13254
|
-
backtest$1.strategyGlobalService.stop(symbol, strategyName);
|
|
13444
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName });
|
|
13255
13445
|
walkerStopSubject.next({ symbol, strategyName });
|
|
13256
13446
|
}
|
|
13257
13447
|
if (!isDone) {
|
package/build/index.mjs
CHANGED
|
@@ -3126,13 +3126,12 @@ class StrategyConnectionService {
|
|
|
3126
3126
|
* @param strategyName - Name of strategy to stop
|
|
3127
3127
|
* @returns Promise that resolves when stop flag is set
|
|
3128
3128
|
*/
|
|
3129
|
-
this.stop = async (
|
|
3129
|
+
this.stop = async (ctx) => {
|
|
3130
3130
|
this.loggerService.log("strategyConnectionService stop", {
|
|
3131
|
-
|
|
3132
|
-
strategyName,
|
|
3131
|
+
ctx
|
|
3133
3132
|
});
|
|
3134
|
-
const strategy = this.getStrategy(symbol, strategyName);
|
|
3135
|
-
await strategy.stop(symbol, strategyName);
|
|
3133
|
+
const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
|
|
3134
|
+
await strategy.stop(ctx.symbol, ctx.strategyName);
|
|
3136
3135
|
};
|
|
3137
3136
|
/**
|
|
3138
3137
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -4063,13 +4062,12 @@ class StrategyGlobalService {
|
|
|
4063
4062
|
* @param strategyName - Name of strategy to stop
|
|
4064
4063
|
* @returns Promise that resolves when stop flag is set
|
|
4065
4064
|
*/
|
|
4066
|
-
this.stop = async (
|
|
4065
|
+
this.stop = async (ctx) => {
|
|
4067
4066
|
this.loggerService.log("strategyGlobalService stop", {
|
|
4068
|
-
|
|
4069
|
-
strategyName,
|
|
4067
|
+
ctx,
|
|
4070
4068
|
});
|
|
4071
|
-
await this.validate(symbol, strategyName);
|
|
4072
|
-
return await this.strategyConnectionService.stop(
|
|
4069
|
+
await this.validate(ctx.symbol, ctx.strategyName);
|
|
4070
|
+
return await this.strategyConnectionService.stop(ctx);
|
|
4073
4071
|
};
|
|
4074
4072
|
/**
|
|
4075
4073
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -5082,7 +5080,7 @@ class LiveLogicPrivateService {
|
|
|
5082
5080
|
await sleep(TICK_TTL);
|
|
5083
5081
|
continue;
|
|
5084
5082
|
}
|
|
5085
|
-
// Yield opened, closed
|
|
5083
|
+
// Yield opened, closed results
|
|
5086
5084
|
yield result;
|
|
5087
5085
|
await sleep(TICK_TTL);
|
|
5088
5086
|
}
|
|
@@ -7262,6 +7260,118 @@ function formatMetric(value) {
|
|
|
7262
7260
|
}
|
|
7263
7261
|
return value.toFixed(2);
|
|
7264
7262
|
}
|
|
7263
|
+
/**
|
|
7264
|
+
* Creates strategy comparison columns based on metric name.
|
|
7265
|
+
* Dynamically builds column configuration with metric-specific header.
|
|
7266
|
+
*
|
|
7267
|
+
* @param metric - Metric being optimized
|
|
7268
|
+
* @returns Array of column configurations for strategy comparison table
|
|
7269
|
+
*/
|
|
7270
|
+
function createStrategyColumns(metric) {
|
|
7271
|
+
return [
|
|
7272
|
+
{
|
|
7273
|
+
key: "rank",
|
|
7274
|
+
label: "Rank",
|
|
7275
|
+
format: (data, index) => `${index + 1}`,
|
|
7276
|
+
},
|
|
7277
|
+
{
|
|
7278
|
+
key: "strategy",
|
|
7279
|
+
label: "Strategy",
|
|
7280
|
+
format: (data) => data.strategyName,
|
|
7281
|
+
},
|
|
7282
|
+
{
|
|
7283
|
+
key: "metric",
|
|
7284
|
+
label: metric,
|
|
7285
|
+
format: (data) => formatMetric(data.metricValue),
|
|
7286
|
+
},
|
|
7287
|
+
{
|
|
7288
|
+
key: "totalSignals",
|
|
7289
|
+
label: "Total Signals",
|
|
7290
|
+
format: (data) => `${data.stats.totalSignals}`,
|
|
7291
|
+
},
|
|
7292
|
+
{
|
|
7293
|
+
key: "winRate",
|
|
7294
|
+
label: "Win Rate",
|
|
7295
|
+
format: (data) => data.stats.winRate !== null
|
|
7296
|
+
? `${data.stats.winRate.toFixed(2)}%`
|
|
7297
|
+
: "N/A",
|
|
7298
|
+
},
|
|
7299
|
+
{
|
|
7300
|
+
key: "avgPnl",
|
|
7301
|
+
label: "Avg PNL",
|
|
7302
|
+
format: (data) => data.stats.avgPnl !== null
|
|
7303
|
+
? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
|
|
7304
|
+
: "N/A",
|
|
7305
|
+
},
|
|
7306
|
+
{
|
|
7307
|
+
key: "totalPnl",
|
|
7308
|
+
label: "Total PNL",
|
|
7309
|
+
format: (data) => data.stats.totalPnl !== null
|
|
7310
|
+
? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
|
|
7311
|
+
: "N/A",
|
|
7312
|
+
},
|
|
7313
|
+
{
|
|
7314
|
+
key: "sharpeRatio",
|
|
7315
|
+
label: "Sharpe Ratio",
|
|
7316
|
+
format: (data) => data.stats.sharpeRatio !== null
|
|
7317
|
+
? `${data.stats.sharpeRatio.toFixed(3)}`
|
|
7318
|
+
: "N/A",
|
|
7319
|
+
},
|
|
7320
|
+
{
|
|
7321
|
+
key: "stdDev",
|
|
7322
|
+
label: "Std Dev",
|
|
7323
|
+
format: (data) => data.stats.stdDev !== null
|
|
7324
|
+
? `${data.stats.stdDev.toFixed(3)}%`
|
|
7325
|
+
: "N/A",
|
|
7326
|
+
},
|
|
7327
|
+
];
|
|
7328
|
+
}
|
|
7329
|
+
/**
|
|
7330
|
+
* Column configuration for PNL table.
|
|
7331
|
+
* Defines all columns for displaying closed signals across strategies.
|
|
7332
|
+
*/
|
|
7333
|
+
const pnlColumns = [
|
|
7334
|
+
{
|
|
7335
|
+
key: "strategy",
|
|
7336
|
+
label: "Strategy",
|
|
7337
|
+
format: (data) => data.strategyName,
|
|
7338
|
+
},
|
|
7339
|
+
{
|
|
7340
|
+
key: "signalId",
|
|
7341
|
+
label: "Signal ID",
|
|
7342
|
+
format: (data) => data.signalId,
|
|
7343
|
+
},
|
|
7344
|
+
{
|
|
7345
|
+
key: "symbol",
|
|
7346
|
+
label: "Symbol",
|
|
7347
|
+
format: (data) => data.symbol,
|
|
7348
|
+
},
|
|
7349
|
+
{
|
|
7350
|
+
key: "position",
|
|
7351
|
+
label: "Position",
|
|
7352
|
+
format: (data) => data.position.toUpperCase(),
|
|
7353
|
+
},
|
|
7354
|
+
{
|
|
7355
|
+
key: "pnl",
|
|
7356
|
+
label: "PNL (net)",
|
|
7357
|
+
format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
|
|
7358
|
+
},
|
|
7359
|
+
{
|
|
7360
|
+
key: "closeReason",
|
|
7361
|
+
label: "Close Reason",
|
|
7362
|
+
format: (data) => data.closeReason,
|
|
7363
|
+
},
|
|
7364
|
+
{
|
|
7365
|
+
key: "openTime",
|
|
7366
|
+
label: "Open Time",
|
|
7367
|
+
format: (data) => new Date(data.openTime).toISOString(),
|
|
7368
|
+
},
|
|
7369
|
+
{
|
|
7370
|
+
key: "closeTime",
|
|
7371
|
+
label: "Close Time",
|
|
7372
|
+
format: (data) => new Date(data.closeTime).toISOString(),
|
|
7373
|
+
},
|
|
7374
|
+
];
|
|
7265
7375
|
/**
|
|
7266
7376
|
* Storage class for accumulating walker results.
|
|
7267
7377
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
@@ -7274,9 +7384,12 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7274
7384
|
this._bestStats = null;
|
|
7275
7385
|
this._bestMetric = null;
|
|
7276
7386
|
this._bestStrategy = null;
|
|
7387
|
+
/** All strategy results for comparison table */
|
|
7388
|
+
this._strategyResults = [];
|
|
7277
7389
|
}
|
|
7278
7390
|
/**
|
|
7279
7391
|
* Adds a strategy result to the storage.
|
|
7392
|
+
* Updates best strategy tracking and accumulates result for comparison table.
|
|
7280
7393
|
*
|
|
7281
7394
|
* @param data - Walker contract with strategy result
|
|
7282
7395
|
*/
|
|
@@ -7290,6 +7403,12 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7290
7403
|
if (data.strategyName === data.bestStrategy) {
|
|
7291
7404
|
this._bestStats = data.stats;
|
|
7292
7405
|
}
|
|
7406
|
+
// Add strategy result to comparison list
|
|
7407
|
+
this._strategyResults.push({
|
|
7408
|
+
strategyName: data.strategyName,
|
|
7409
|
+
stats: data.stats,
|
|
7410
|
+
metricValue: data.metricValue,
|
|
7411
|
+
});
|
|
7293
7412
|
}
|
|
7294
7413
|
/**
|
|
7295
7414
|
* Calculates walker results from strategy results.
|
|
@@ -7314,10 +7433,79 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7314
7433
|
bestStrategy: this._bestStrategy,
|
|
7315
7434
|
bestMetric: this._bestMetric,
|
|
7316
7435
|
bestStats: this._bestStats,
|
|
7436
|
+
strategyResults: this._strategyResults,
|
|
7317
7437
|
};
|
|
7318
7438
|
}
|
|
7439
|
+
/**
|
|
7440
|
+
* Generates comparison table for top N strategies (View).
|
|
7441
|
+
* Sorts strategies by metric value and formats as markdown table.
|
|
7442
|
+
*
|
|
7443
|
+
* @param metric - Metric being optimized
|
|
7444
|
+
* @param topN - Number of top strategies to include (default: 10)
|
|
7445
|
+
* @returns Markdown formatted comparison table
|
|
7446
|
+
*/
|
|
7447
|
+
getComparisonTable(metric, topN = 10) {
|
|
7448
|
+
if (this._strategyResults.length === 0) {
|
|
7449
|
+
return "No strategy results available.";
|
|
7450
|
+
}
|
|
7451
|
+
// Sort strategies by metric value (descending)
|
|
7452
|
+
const sortedResults = [...this._strategyResults].sort((a, b) => {
|
|
7453
|
+
const aValue = a.metricValue ?? -Infinity;
|
|
7454
|
+
const bValue = b.metricValue ?? -Infinity;
|
|
7455
|
+
return bValue - aValue;
|
|
7456
|
+
});
|
|
7457
|
+
// Take top N strategies
|
|
7458
|
+
const topStrategies = sortedResults.slice(0, topN);
|
|
7459
|
+
// Get columns configuration
|
|
7460
|
+
const columns = createStrategyColumns(metric);
|
|
7461
|
+
// Build table header
|
|
7462
|
+
const header = columns.map((col) => col.label);
|
|
7463
|
+
const separator = columns.map(() => "---");
|
|
7464
|
+
// Build table rows
|
|
7465
|
+
const rows = topStrategies.map((result, index) => columns.map((col) => col.format(result, index)));
|
|
7466
|
+
const tableData = [header, separator, ...rows];
|
|
7467
|
+
return str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
|
|
7468
|
+
}
|
|
7469
|
+
/**
|
|
7470
|
+
* Generates PNL table showing all closed signals across all strategies (View).
|
|
7471
|
+
* Collects all signals from all strategies and formats as markdown table.
|
|
7472
|
+
*
|
|
7473
|
+
* @returns Markdown formatted PNL table
|
|
7474
|
+
*/
|
|
7475
|
+
getPnlTable() {
|
|
7476
|
+
if (this._strategyResults.length === 0) {
|
|
7477
|
+
return "No strategy results available.";
|
|
7478
|
+
}
|
|
7479
|
+
// Collect all closed signals from all strategies
|
|
7480
|
+
const allSignals = [];
|
|
7481
|
+
for (const result of this._strategyResults) {
|
|
7482
|
+
for (const signal of result.stats.signalList) {
|
|
7483
|
+
allSignals.push({
|
|
7484
|
+
strategyName: result.strategyName,
|
|
7485
|
+
signalId: signal.signal.id,
|
|
7486
|
+
symbol: signal.signal.symbol,
|
|
7487
|
+
position: signal.signal.position,
|
|
7488
|
+
pnl: signal.pnl.pnlPercentage,
|
|
7489
|
+
closeReason: signal.closeReason,
|
|
7490
|
+
openTime: signal.signal.pendingAt,
|
|
7491
|
+
closeTime: signal.closeTimestamp,
|
|
7492
|
+
});
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
if (allSignals.length === 0) {
|
|
7496
|
+
return "No closed signals available.";
|
|
7497
|
+
}
|
|
7498
|
+
// Build table header
|
|
7499
|
+
const header = pnlColumns.map((col) => col.label);
|
|
7500
|
+
const separator = pnlColumns.map(() => "---");
|
|
7501
|
+
// Build table rows
|
|
7502
|
+
const rows = allSignals.map((signal) => pnlColumns.map((col) => col.format(signal)));
|
|
7503
|
+
const tableData = [header, separator, ...rows];
|
|
7504
|
+
return str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
|
|
7505
|
+
}
|
|
7319
7506
|
/**
|
|
7320
7507
|
* Generates markdown report with all strategy results (View).
|
|
7508
|
+
* Includes best strategy summary, comparison table, and PNL table.
|
|
7321
7509
|
*
|
|
7322
7510
|
* @param symbol - Trading symbol
|
|
7323
7511
|
* @param metric - Metric being optimized
|
|
@@ -7326,7 +7514,9 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7326
7514
|
*/
|
|
7327
7515
|
async getReport(symbol, metric, context) {
|
|
7328
7516
|
const results = await this.getData(symbol, metric, context);
|
|
7329
|
-
|
|
7517
|
+
// Get total signals for best strategy
|
|
7518
|
+
const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
|
|
7519
|
+
return str.newline(`# Walker Comparison Report: ${results.walkerName}`, "", `**Symbol:** ${results.symbol}`, `**Exchange:** ${results.exchangeName}`, `**Frame:** ${results.frameName}`, `**Optimization Metric:** ${results.metric}`, `**Strategies Tested:** ${results.totalStrategies}`, "", `## Best Strategy: ${results.bestStrategy}`, "", `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`, `**Total Signals:** ${bestStrategySignals}`, "", "## Top Strategies Comparison", "", this.getComparisonTable(metric, 10), "", "## All Signals (PNL Table)", "", this.getPnlTable(), "", "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better).");
|
|
7330
7520
|
}
|
|
7331
7521
|
/**
|
|
7332
7522
|
* Saves walker report to disk.
|
|
@@ -12604,7 +12794,7 @@ class BacktestUtils {
|
|
|
12604
12794
|
};
|
|
12605
12795
|
task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
12606
12796
|
return () => {
|
|
12607
|
-
backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
|
|
12797
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
12608
12798
|
backtest$1.strategyGlobalService
|
|
12609
12799
|
.getPendingSignal(symbol, context.strategyName)
|
|
12610
12800
|
.then(async (pendingSignal) => {
|
|
@@ -12817,7 +13007,7 @@ class LiveUtils {
|
|
|
12817
13007
|
};
|
|
12818
13008
|
task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
12819
13009
|
return () => {
|
|
12820
|
-
backtest$1.strategyGlobalService.stop(symbol, context.strategyName);
|
|
13010
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName: context.strategyName });
|
|
12821
13011
|
backtest$1.strategyGlobalService
|
|
12822
13012
|
.getPendingSignal(symbol, context.strategyName)
|
|
12823
13013
|
.then(async (pendingSignal) => {
|
|
@@ -13249,7 +13439,7 @@ class WalkerUtils {
|
|
|
13249
13439
|
task().catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
13250
13440
|
return () => {
|
|
13251
13441
|
for (const strategyName of walkerSchema.strategies) {
|
|
13252
|
-
backtest$1.strategyGlobalService.stop(symbol, strategyName);
|
|
13442
|
+
backtest$1.strategyGlobalService.stop({ symbol, strategyName });
|
|
13253
13443
|
walkerStopSubject.next({ symbol, strategyName });
|
|
13254
13444
|
}
|
|
13255
13445
|
if (!isDone) {
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -4258,8 +4258,24 @@ declare class PerformanceMarkdownService {
|
|
|
4258
4258
|
* Alias for walker statistics result interface.
|
|
4259
4259
|
* Used for clarity in markdown service context.
|
|
4260
4260
|
*
|
|
4261
|
+
* Extends IWalkerResults with additional strategy comparison data.
|
|
4261
4262
|
*/
|
|
4262
|
-
|
|
4263
|
+
interface WalkerStatistics extends IWalkerResults {
|
|
4264
|
+
/** Array of all strategy results for comparison and analysis */
|
|
4265
|
+
strategyResults: IStrategyResult[];
|
|
4266
|
+
}
|
|
4267
|
+
/**
|
|
4268
|
+
* Strategy result entry for comparison table.
|
|
4269
|
+
* Contains strategy name, full statistics, and metric value for ranking.
|
|
4270
|
+
*/
|
|
4271
|
+
interface IStrategyResult {
|
|
4272
|
+
/** Strategy name */
|
|
4273
|
+
strategyName: StrategyName;
|
|
4274
|
+
/** Complete backtest statistics for this strategy */
|
|
4275
|
+
stats: BacktestStatistics;
|
|
4276
|
+
/** Value of the optimization metric (null if invalid) */
|
|
4277
|
+
metricValue: number | null;
|
|
4278
|
+
}
|
|
4263
4279
|
/**
|
|
4264
4280
|
* Service for generating and saving walker markdown reports.
|
|
4265
4281
|
*
|
|
@@ -5063,7 +5079,7 @@ declare class BacktestUtils {
|
|
|
5063
5079
|
strategyName: string;
|
|
5064
5080
|
exchangeName: string;
|
|
5065
5081
|
frameName: string;
|
|
5066
|
-
}) => AsyncGenerator<
|
|
5082
|
+
}) => AsyncGenerator<IStrategyBacktestResult, void, unknown>;
|
|
5067
5083
|
/**
|
|
5068
5084
|
* Runs backtest in background without yielding results.
|
|
5069
5085
|
*
|
|
@@ -5198,7 +5214,7 @@ declare class LiveUtils {
|
|
|
5198
5214
|
run: (symbol: string, context: {
|
|
5199
5215
|
strategyName: string;
|
|
5200
5216
|
exchangeName: string;
|
|
5201
|
-
}) => AsyncGenerator<
|
|
5217
|
+
}) => AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
|
|
5202
5218
|
/**
|
|
5203
5219
|
* Runs live trading in background without yielding results.
|
|
5204
5220
|
*
|
|
@@ -6520,7 +6536,10 @@ declare class StrategyConnectionService {
|
|
|
6520
6536
|
* @param strategyName - Name of strategy to stop
|
|
6521
6537
|
* @returns Promise that resolves when stop flag is set
|
|
6522
6538
|
*/
|
|
6523
|
-
stop: (
|
|
6539
|
+
stop: (ctx: {
|
|
6540
|
+
symbol: string;
|
|
6541
|
+
strategyName: StrategyName;
|
|
6542
|
+
}) => Promise<void>;
|
|
6524
6543
|
/**
|
|
6525
6544
|
* Clears the memoized ClientStrategy instance from cache.
|
|
6526
6545
|
*
|
|
@@ -6988,7 +7007,10 @@ declare class StrategyGlobalService {
|
|
|
6988
7007
|
* @param strategyName - Name of strategy to stop
|
|
6989
7008
|
* @returns Promise that resolves when stop flag is set
|
|
6990
7009
|
*/
|
|
6991
|
-
stop: (
|
|
7010
|
+
stop: (ctx: {
|
|
7011
|
+
symbol: string;
|
|
7012
|
+
strategyName: StrategyName;
|
|
7013
|
+
}) => Promise<void>;
|
|
6992
7014
|
/**
|
|
6993
7015
|
* Clears the memoized ClientStrategy instance from cache.
|
|
6994
7016
|
*
|
|
@@ -7451,7 +7473,7 @@ declare class BacktestLogicPrivateService {
|
|
|
7451
7473
|
* }
|
|
7452
7474
|
* ```
|
|
7453
7475
|
*/
|
|
7454
|
-
run(symbol: string): AsyncGenerator<
|
|
7476
|
+
run(symbol: string): AsyncGenerator<IStrategyBacktestResult, void, unknown>;
|
|
7455
7477
|
}
|
|
7456
7478
|
|
|
7457
7479
|
/**
|
|
@@ -7496,7 +7518,7 @@ declare class LiveLogicPrivateService {
|
|
|
7496
7518
|
* }
|
|
7497
7519
|
* ```
|
|
7498
7520
|
*/
|
|
7499
|
-
run(symbol: string): AsyncGenerator<
|
|
7521
|
+
run(symbol: string): AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
|
|
7500
7522
|
}
|
|
7501
7523
|
|
|
7502
7524
|
/**
|
|
@@ -7590,7 +7612,7 @@ declare class BacktestLogicPublicService {
|
|
|
7590
7612
|
strategyName: string;
|
|
7591
7613
|
exchangeName: string;
|
|
7592
7614
|
frameName: string;
|
|
7593
|
-
}) => AsyncGenerator<
|
|
7615
|
+
}) => AsyncGenerator<IStrategyBacktestResult, void, unknown>;
|
|
7594
7616
|
}
|
|
7595
7617
|
|
|
7596
7618
|
/**
|
|
@@ -7641,7 +7663,7 @@ declare class LiveLogicPublicService {
|
|
|
7641
7663
|
run: (symbol: string, context: {
|
|
7642
7664
|
strategyName: string;
|
|
7643
7665
|
exchangeName: string;
|
|
7644
|
-
}) => AsyncGenerator<
|
|
7666
|
+
}) => AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
|
|
7645
7667
|
}
|
|
7646
7668
|
|
|
7647
7669
|
/**
|
|
@@ -7709,7 +7731,7 @@ declare class LiveCommandService {
|
|
|
7709
7731
|
run: (symbol: string, context: {
|
|
7710
7732
|
strategyName: string;
|
|
7711
7733
|
exchangeName: string;
|
|
7712
|
-
}) => AsyncGenerator<
|
|
7734
|
+
}) => AsyncGenerator<IStrategyTickResultOpened | IStrategyTickResultClosed, void, unknown>;
|
|
7713
7735
|
}
|
|
7714
7736
|
|
|
7715
7737
|
/**
|
|
@@ -7737,7 +7759,7 @@ declare class BacktestCommandService {
|
|
|
7737
7759
|
strategyName: string;
|
|
7738
7760
|
exchangeName: string;
|
|
7739
7761
|
frameName: string;
|
|
7740
|
-
}) => AsyncGenerator<
|
|
7762
|
+
}) => AsyncGenerator<IStrategyBacktestResult, void, unknown>;
|
|
7741
7763
|
}
|
|
7742
7764
|
|
|
7743
7765
|
/**
|