backtest-kit 1.5.25 → 1.5.27
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 +3 -3
- package/build/index.cjs +1789 -1055
- package/build/index.mjs +1788 -1057
- package/package.json +1 -1
- package/types.d.ts +4549 -3961
package/build/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, ToolRegistry, isObject, and, resolveDocuments,
|
|
1
|
+
import { str, errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, ToolRegistry, isObject, and, resolveDocuments, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
|
|
2
2
|
import { createActivator } from 'di-kit';
|
|
3
3
|
import { scoped } from 'di-scoped';
|
|
4
4
|
import fs, { mkdir, writeFile } from 'fs/promises';
|
|
@@ -120,6 +120,1081 @@ const GLOBAL_CONFIG = {
|
|
|
120
120
|
};
|
|
121
121
|
const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Converts markdown content to plain text with minimal formatting
|
|
125
|
+
* @param content - Markdown string to convert
|
|
126
|
+
* @returns Plain text representation
|
|
127
|
+
*/
|
|
128
|
+
const toPlainString = (content) => {
|
|
129
|
+
if (!content) {
|
|
130
|
+
return "";
|
|
131
|
+
}
|
|
132
|
+
let text = content;
|
|
133
|
+
// Remove code blocks
|
|
134
|
+
text = text.replace(/```[\s\S]*?```/g, "");
|
|
135
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
136
|
+
// Remove images
|
|
137
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
138
|
+
// Convert links to text only (keep link text, remove URL)
|
|
139
|
+
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
140
|
+
// Remove headers (convert to plain text)
|
|
141
|
+
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
142
|
+
// Remove bold and italic markers
|
|
143
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
144
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
145
|
+
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
146
|
+
text = text.replace(/___(.+?)___/g, "$1");
|
|
147
|
+
text = text.replace(/__(.+?)__/g, "$1");
|
|
148
|
+
text = text.replace(/_(.+?)_/g, "$1");
|
|
149
|
+
// Remove strikethrough
|
|
150
|
+
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
151
|
+
// Convert lists to plain text with bullets
|
|
152
|
+
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
153
|
+
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
154
|
+
// Remove blockquotes
|
|
155
|
+
text = text.replace(/^\s*>\s+/gm, "");
|
|
156
|
+
// Remove horizontal rules
|
|
157
|
+
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
158
|
+
// Remove HTML tags
|
|
159
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
160
|
+
// Remove excessive whitespace and normalize line breaks
|
|
161
|
+
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
162
|
+
text = text.replace(/[ \t]+/g, " ");
|
|
163
|
+
// Remove all newline characters
|
|
164
|
+
text = text.replace(/\n/g, " ");
|
|
165
|
+
// Remove excessive spaces after newline removal
|
|
166
|
+
text = text.replace(/\s+/g, " ");
|
|
167
|
+
return text.trim();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Column configuration for backtest markdown reports.
|
|
172
|
+
*
|
|
173
|
+
* Defines the table structure for displaying closed trading signals in backtest reports.
|
|
174
|
+
* Each column specifies how to format and display specific signal data fields.
|
|
175
|
+
*
|
|
176
|
+
* Used by {@link BacktestMarkdownService} to generate markdown tables showing:
|
|
177
|
+
* - Signal identification (ID, symbol, position)
|
|
178
|
+
* - Price levels (open, close, take profit, stop loss)
|
|
179
|
+
* - Performance metrics (PNL percentage, close reason)
|
|
180
|
+
* - Timing information (duration, timestamps)
|
|
181
|
+
*
|
|
182
|
+
* @remarks
|
|
183
|
+
* Columns can be conditionally visible based on {@link GLOBAL_CONFIG} settings.
|
|
184
|
+
* For example, the "note" column visibility is controlled by `CC_REPORT_SHOW_SIGNAL_NOTE`.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* import { backtest_columns } from "./assets/backtest.columns";
|
|
189
|
+
*
|
|
190
|
+
* // Use with BacktestMarkdownService
|
|
191
|
+
* const service = new BacktestMarkdownService();
|
|
192
|
+
* await service.getReport("BTCUSDT", "my-strategy", backtest_columns);
|
|
193
|
+
*
|
|
194
|
+
* // Or customize columns
|
|
195
|
+
* const customColumns = backtest_columns.filter(col => col.key !== "note");
|
|
196
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* @see {@link BacktestMarkdownService} for usage in report generation
|
|
200
|
+
* @see {@link ColumnModel} for column interface definition
|
|
201
|
+
* @see {@link IStrategyTickResultClosed} for data structure
|
|
202
|
+
*/
|
|
203
|
+
const backtest_columns = [
|
|
204
|
+
{
|
|
205
|
+
key: "signalId",
|
|
206
|
+
label: "Signal ID",
|
|
207
|
+
format: (data) => data.signal.id,
|
|
208
|
+
isVisible: () => true,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
key: "symbol",
|
|
212
|
+
label: "Symbol",
|
|
213
|
+
format: (data) => data.signal.symbol,
|
|
214
|
+
isVisible: () => true,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
key: "position",
|
|
218
|
+
label: "Position",
|
|
219
|
+
format: (data) => data.signal.position.toUpperCase(),
|
|
220
|
+
isVisible: () => true,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
key: "note",
|
|
224
|
+
label: "Note",
|
|
225
|
+
format: (data) => toPlainString(data.signal.note ?? "N/A"),
|
|
226
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
key: "openPrice",
|
|
230
|
+
label: "Open Price",
|
|
231
|
+
format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
|
|
232
|
+
isVisible: () => true,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
key: "closePrice",
|
|
236
|
+
label: "Close Price",
|
|
237
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
238
|
+
isVisible: () => true,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
key: "takeProfit",
|
|
242
|
+
label: "Take Profit",
|
|
243
|
+
format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
|
|
244
|
+
isVisible: () => true,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
key: "stopLoss",
|
|
248
|
+
label: "Stop Loss",
|
|
249
|
+
format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
|
|
250
|
+
isVisible: () => true,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
key: "pnl",
|
|
254
|
+
label: "PNL (net)",
|
|
255
|
+
format: (data) => {
|
|
256
|
+
const pnlPercentage = data.pnl.pnlPercentage;
|
|
257
|
+
return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
|
|
258
|
+
},
|
|
259
|
+
isVisible: () => true,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: "closeReason",
|
|
263
|
+
label: "Close Reason",
|
|
264
|
+
format: (data) => data.closeReason,
|
|
265
|
+
isVisible: () => true,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
key: "duration",
|
|
269
|
+
label: "Duration (min)",
|
|
270
|
+
format: (data) => {
|
|
271
|
+
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
272
|
+
const durationMin = Math.round(durationMs / 60000);
|
|
273
|
+
return `${durationMin}`;
|
|
274
|
+
},
|
|
275
|
+
isVisible: () => true,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
key: "openTimestamp",
|
|
279
|
+
label: "Open Time",
|
|
280
|
+
format: (data) => new Date(data.signal.pendingAt).toISOString(),
|
|
281
|
+
isVisible: () => true,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
key: "closeTimestamp",
|
|
285
|
+
label: "Close Time",
|
|
286
|
+
format: (data) => new Date(data.closeTimestamp).toISOString(),
|
|
287
|
+
isVisible: () => true,
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Column configuration for portfolio heatmap markdown reports.
|
|
293
|
+
*
|
|
294
|
+
* Defines the table structure for displaying aggregated per-symbol statistics in portfolio heatmap reports.
|
|
295
|
+
* Each column specifies how to format and display portfolio performance metrics across different trading symbols.
|
|
296
|
+
*
|
|
297
|
+
* Used by {@link HeatMarkdownService} to generate markdown tables showing:
|
|
298
|
+
* - Symbol identification
|
|
299
|
+
* - Performance metrics (Total PNL, Sharpe Ratio, Profit Factor, Expectancy)
|
|
300
|
+
* - Risk metrics (Win Rate, Average Win/Loss, Max Drawdown)
|
|
301
|
+
* - Trading activity (Total Trades, Win/Loss Streaks)
|
|
302
|
+
*
|
|
303
|
+
* @remarks
|
|
304
|
+
* This configuration is used to create portfolio-wide views that aggregate statistics per symbol.
|
|
305
|
+
* The heatmap service automatically sorts symbols by Sharpe Ratio (best performers first).
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* import { heat_columns } from "./assets/heat.columns";
|
|
310
|
+
*
|
|
311
|
+
* // Use with HeatMarkdownService
|
|
312
|
+
* const service = new HeatMarkdownService();
|
|
313
|
+
* await service.getReport("my-strategy", heat_columns);
|
|
314
|
+
*
|
|
315
|
+
* // Or customize to show only key metrics
|
|
316
|
+
* const customColumns = heat_columns.filter(col =>
|
|
317
|
+
* ["symbol", "totalPnl", "sharpeRatio", "totalTrades"].includes(col.key)
|
|
318
|
+
* );
|
|
319
|
+
* await service.getReport("my-strategy", customColumns);
|
|
320
|
+
* ```
|
|
321
|
+
*
|
|
322
|
+
* @see {@link HeatMarkdownService} for usage in report generation
|
|
323
|
+
* @see {@link ColumnModel} for column interface definition
|
|
324
|
+
* @see {@link IHeatmapRow} for data structure
|
|
325
|
+
*/
|
|
326
|
+
const heat_columns = [
|
|
327
|
+
{
|
|
328
|
+
key: "symbol",
|
|
329
|
+
label: "Symbol",
|
|
330
|
+
format: (data) => data.symbol,
|
|
331
|
+
isVisible: () => true,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
key: "totalPnl",
|
|
335
|
+
label: "Total PNL",
|
|
336
|
+
format: (data) => data.totalPnl !== null ? str(data.totalPnl, "%+.2f%%") : "N/A",
|
|
337
|
+
isVisible: () => true,
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
key: "sharpeRatio",
|
|
341
|
+
label: "Sharpe",
|
|
342
|
+
format: (data) => data.sharpeRatio !== null ? str(data.sharpeRatio, "%.2f") : "N/A",
|
|
343
|
+
isVisible: () => true,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
key: "profitFactor",
|
|
347
|
+
label: "PF",
|
|
348
|
+
format: (data) => data.profitFactor !== null ? str(data.profitFactor, "%.2f") : "N/A",
|
|
349
|
+
isVisible: () => true,
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: "expectancy",
|
|
353
|
+
label: "Expect",
|
|
354
|
+
format: (data) => data.expectancy !== null ? str(data.expectancy, "%+.2f%%") : "N/A",
|
|
355
|
+
isVisible: () => true,
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
key: "winRate",
|
|
359
|
+
label: "WR",
|
|
360
|
+
format: (data) => data.winRate !== null ? str(data.winRate, "%.1f%%") : "N/A",
|
|
361
|
+
isVisible: () => true,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
key: "avgWin",
|
|
365
|
+
label: "Avg Win",
|
|
366
|
+
format: (data) => data.avgWin !== null ? str(data.avgWin, "%+.2f%%") : "N/A",
|
|
367
|
+
isVisible: () => true,
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
key: "avgLoss",
|
|
371
|
+
label: "Avg Loss",
|
|
372
|
+
format: (data) => data.avgLoss !== null ? str(data.avgLoss, "%+.2f%%") : "N/A",
|
|
373
|
+
isVisible: () => true,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
key: "maxDrawdown",
|
|
377
|
+
label: "Max DD",
|
|
378
|
+
format: (data) => data.maxDrawdown !== null ? str(-data.maxDrawdown, "%.2f%%") : "N/A",
|
|
379
|
+
isVisible: () => true,
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
key: "maxWinStreak",
|
|
383
|
+
label: "W Streak",
|
|
384
|
+
format: (data) => data.maxWinStreak.toString(),
|
|
385
|
+
isVisible: () => true,
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
key: "maxLossStreak",
|
|
389
|
+
label: "L Streak",
|
|
390
|
+
format: (data) => data.maxLossStreak.toString(),
|
|
391
|
+
isVisible: () => true,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
key: "totalTrades",
|
|
395
|
+
label: "Trades",
|
|
396
|
+
format: (data) => data.totalTrades.toString(),
|
|
397
|
+
isVisible: () => true,
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Column configuration for live trading markdown reports.
|
|
403
|
+
*
|
|
404
|
+
* Defines the table structure for displaying real-time trading events in live trading reports.
|
|
405
|
+
* Each column specifies how to format and display live trading event data fields.
|
|
406
|
+
*
|
|
407
|
+
* Used by {@link LiveMarkdownService} to generate markdown tables showing:
|
|
408
|
+
* - Event information (timestamp, action type)
|
|
409
|
+
* - Signal identification (symbol, signal ID, position)
|
|
410
|
+
* - Price data (current price, open price, take profit, stop loss)
|
|
411
|
+
* - Performance metrics (PNL percentage, close reason, duration)
|
|
412
|
+
* - Position tracking (percentage to TP/SL)
|
|
413
|
+
*
|
|
414
|
+
* @remarks
|
|
415
|
+
* This configuration tracks all event types: idle, opened, active, and closed signals.
|
|
416
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```typescript
|
|
420
|
+
* import { live_columns } from "./assets/live.columns";
|
|
421
|
+
*
|
|
422
|
+
* // Use with LiveMarkdownService
|
|
423
|
+
* const service = new LiveMarkdownService();
|
|
424
|
+
* await service.getReport("BTCUSDT", "my-strategy", live_columns);
|
|
425
|
+
*
|
|
426
|
+
* // Or customize for minimal display
|
|
427
|
+
* const customColumns = live_columns.filter(col =>
|
|
428
|
+
* ["timestamp", "action", "symbol", "pnl"].includes(col.key)
|
|
429
|
+
* );
|
|
430
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
431
|
+
* ```
|
|
432
|
+
*
|
|
433
|
+
* @see {@link LiveMarkdownService} for usage in report generation
|
|
434
|
+
* @see {@link ColumnModel} for column interface definition
|
|
435
|
+
* @see {@link TickEvent} for data structure
|
|
436
|
+
*/
|
|
437
|
+
const live_columns = [
|
|
438
|
+
{
|
|
439
|
+
key: "timestamp",
|
|
440
|
+
label: "Timestamp",
|
|
441
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
442
|
+
isVisible: () => true,
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
key: "action",
|
|
446
|
+
label: "Action",
|
|
447
|
+
format: (data) => data.action.toUpperCase(),
|
|
448
|
+
isVisible: () => true,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
key: "symbol",
|
|
452
|
+
label: "Symbol",
|
|
453
|
+
format: (data) => data.symbol ?? "N/A",
|
|
454
|
+
isVisible: () => true,
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
key: "signalId",
|
|
458
|
+
label: "Signal ID",
|
|
459
|
+
format: (data) => data.signalId ?? "N/A",
|
|
460
|
+
isVisible: () => true,
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
key: "position",
|
|
464
|
+
label: "Position",
|
|
465
|
+
format: (data) => data.position?.toUpperCase() ?? "N/A",
|
|
466
|
+
isVisible: () => true,
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
key: "note",
|
|
470
|
+
label: "Note",
|
|
471
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
472
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
key: "currentPrice",
|
|
476
|
+
label: "Current Price",
|
|
477
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
478
|
+
isVisible: () => true,
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
key: "openPrice",
|
|
482
|
+
label: "Open Price",
|
|
483
|
+
format: (data) => data.openPrice !== undefined ? `${data.openPrice.toFixed(8)} USD` : "N/A",
|
|
484
|
+
isVisible: () => true,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
key: "takeProfit",
|
|
488
|
+
label: "Take Profit",
|
|
489
|
+
format: (data) => data.takeProfit !== undefined
|
|
490
|
+
? `${data.takeProfit.toFixed(8)} USD`
|
|
491
|
+
: "N/A",
|
|
492
|
+
isVisible: () => true,
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
key: "stopLoss",
|
|
496
|
+
label: "Stop Loss",
|
|
497
|
+
format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
|
|
498
|
+
isVisible: () => true,
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
key: "percentTp",
|
|
502
|
+
label: "% to TP",
|
|
503
|
+
format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
|
|
504
|
+
isVisible: () => true,
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
key: "percentSl",
|
|
508
|
+
label: "% to SL",
|
|
509
|
+
format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
|
|
510
|
+
isVisible: () => true,
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
key: "pnl",
|
|
514
|
+
label: "PNL (net)",
|
|
515
|
+
format: (data) => {
|
|
516
|
+
if (data.pnl === undefined)
|
|
517
|
+
return "N/A";
|
|
518
|
+
return `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`;
|
|
519
|
+
},
|
|
520
|
+
isVisible: () => true,
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
key: "closeReason",
|
|
524
|
+
label: "Close Reason",
|
|
525
|
+
format: (data) => data.closeReason ?? "N/A",
|
|
526
|
+
isVisible: () => true,
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
key: "duration",
|
|
530
|
+
label: "Duration (min)",
|
|
531
|
+
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
532
|
+
isVisible: () => true,
|
|
533
|
+
},
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Column configuration for partial profit/loss markdown reports.
|
|
538
|
+
*
|
|
539
|
+
* Defines the table structure for displaying partial position exit events in trading reports.
|
|
540
|
+
* Each column specifies how to format and display partial profit and loss level events.
|
|
541
|
+
*
|
|
542
|
+
* Used by {@link PartialMarkdownService} to generate markdown tables showing:
|
|
543
|
+
* - Event information (action type: profit or loss)
|
|
544
|
+
* - Signal identification (symbol, strategy name, signal ID, position)
|
|
545
|
+
* - Exit level information (percentage level reached)
|
|
546
|
+
* - Price data (current price at partial exit)
|
|
547
|
+
* - Timing information (timestamp, mode: backtest or live)
|
|
548
|
+
*
|
|
549
|
+
* @remarks
|
|
550
|
+
* This configuration tracks partial position exits at predefined profit/loss levels.
|
|
551
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
552
|
+
* Useful for analyzing risk management strategies and partial exit performance.
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```typescript
|
|
556
|
+
* import { partial_columns } from "./assets/partial.columns";
|
|
557
|
+
*
|
|
558
|
+
* // Use with PartialMarkdownService
|
|
559
|
+
* const service = new PartialMarkdownService();
|
|
560
|
+
* await service.getReport("BTCUSDT", "my-strategy", partial_columns);
|
|
561
|
+
*
|
|
562
|
+
* // Or customize to show only key fields
|
|
563
|
+
* const customColumns = partial_columns.filter(col =>
|
|
564
|
+
* ["action", "symbol", "level", "timestamp"].includes(col.key)
|
|
565
|
+
* );
|
|
566
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
567
|
+
* ```
|
|
568
|
+
*
|
|
569
|
+
* @see {@link PartialMarkdownService} for usage in report generation
|
|
570
|
+
* @see {@link ColumnModel} for column interface definition
|
|
571
|
+
* @see {@link PartialEvent} for data structure
|
|
572
|
+
*/
|
|
573
|
+
const partial_columns = [
|
|
574
|
+
{
|
|
575
|
+
key: "action",
|
|
576
|
+
label: "Action",
|
|
577
|
+
format: (data) => data.action.toUpperCase(),
|
|
578
|
+
isVisible: () => true,
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
key: "symbol",
|
|
582
|
+
label: "Symbol",
|
|
583
|
+
format: (data) => data.symbol,
|
|
584
|
+
isVisible: () => true,
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
key: "strategyName",
|
|
588
|
+
label: "Strategy",
|
|
589
|
+
format: (data) => data.strategyName,
|
|
590
|
+
isVisible: () => true,
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
key: "signalId",
|
|
594
|
+
label: "Signal ID",
|
|
595
|
+
format: (data) => data.signalId,
|
|
596
|
+
isVisible: () => true,
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
key: "position",
|
|
600
|
+
label: "Position",
|
|
601
|
+
format: (data) => data.position.toUpperCase(),
|
|
602
|
+
isVisible: () => true,
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
key: "level",
|
|
606
|
+
label: "Level %",
|
|
607
|
+
format: (data) => data.action === "profit" ? `+${data.level}%` : `-${data.level}%`,
|
|
608
|
+
isVisible: () => true,
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
key: "currentPrice",
|
|
612
|
+
label: "Current Price",
|
|
613
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
614
|
+
isVisible: () => true,
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
key: "timestamp",
|
|
618
|
+
label: "Timestamp",
|
|
619
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
620
|
+
isVisible: () => true,
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
key: "mode",
|
|
624
|
+
label: "Mode",
|
|
625
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
626
|
+
isVisible: () => true,
|
|
627
|
+
},
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Column configuration for performance metrics markdown reports.
|
|
632
|
+
*
|
|
633
|
+
* Defines the table structure for displaying performance statistics and timing metrics in performance reports.
|
|
634
|
+
* Each column specifies how to format and display aggregated performance data for different metric types.
|
|
635
|
+
*
|
|
636
|
+
* Used by {@link PerformanceMarkdownService} to generate markdown tables showing:
|
|
637
|
+
* - Metric identification (metric type)
|
|
638
|
+
* - Execution statistics (count, total duration, average, min, max)
|
|
639
|
+
* - Distribution metrics (standard deviation, median, percentiles)
|
|
640
|
+
* - Wait time statistics (average, min, max wait times between events)
|
|
641
|
+
*
|
|
642
|
+
* @remarks
|
|
643
|
+
* This configuration helps identify performance bottlenecks in strategy execution.
|
|
644
|
+
* The service automatically sorts metrics by total duration to highlight the slowest operations.
|
|
645
|
+
* All durations are measured in milliseconds.
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```typescript
|
|
649
|
+
* import { performance_columns } from "./assets/performance.columns";
|
|
650
|
+
*
|
|
651
|
+
* // Use with PerformanceMarkdownService
|
|
652
|
+
* const service = new PerformanceMarkdownService();
|
|
653
|
+
* await service.getReport("BTCUSDT", "my-strategy", performance_columns);
|
|
654
|
+
*
|
|
655
|
+
* // Or customize for bottleneck analysis
|
|
656
|
+
* const customColumns = performance_columns.filter(col =>
|
|
657
|
+
* ["metricType", "count", "avgDuration", "maxDuration", "p95"].includes(col.key)
|
|
658
|
+
* );
|
|
659
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
660
|
+
* ```
|
|
661
|
+
*
|
|
662
|
+
* @see {@link PerformanceMarkdownService} for usage in report generation
|
|
663
|
+
* @see {@link ColumnModel} for column interface definition
|
|
664
|
+
* @see {@link MetricStats} for data structure
|
|
665
|
+
*/
|
|
666
|
+
const performance_columns = [
|
|
667
|
+
{
|
|
668
|
+
key: "metricType",
|
|
669
|
+
label: "Metric Type",
|
|
670
|
+
format: (data) => data.metricType,
|
|
671
|
+
isVisible: () => true,
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
key: "count",
|
|
675
|
+
label: "Count",
|
|
676
|
+
format: (data) => data.count.toString(),
|
|
677
|
+
isVisible: () => true,
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
key: "totalDuration",
|
|
681
|
+
label: "Total (ms)",
|
|
682
|
+
format: (data) => data.totalDuration.toFixed(2),
|
|
683
|
+
isVisible: () => true,
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
key: "avgDuration",
|
|
687
|
+
label: "Avg (ms)",
|
|
688
|
+
format: (data) => data.avgDuration.toFixed(2),
|
|
689
|
+
isVisible: () => true,
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
key: "minDuration",
|
|
693
|
+
label: "Min (ms)",
|
|
694
|
+
format: (data) => data.minDuration.toFixed(2),
|
|
695
|
+
isVisible: () => true,
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
key: "maxDuration",
|
|
699
|
+
label: "Max (ms)",
|
|
700
|
+
format: (data) => data.maxDuration.toFixed(2),
|
|
701
|
+
isVisible: () => true,
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
key: "stdDev",
|
|
705
|
+
label: "Std Dev (ms)",
|
|
706
|
+
format: (data) => data.stdDev.toFixed(2),
|
|
707
|
+
isVisible: () => true,
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
key: "median",
|
|
711
|
+
label: "Median (ms)",
|
|
712
|
+
format: (data) => data.median.toFixed(2),
|
|
713
|
+
isVisible: () => true,
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
key: "p95",
|
|
717
|
+
label: "P95 (ms)",
|
|
718
|
+
format: (data) => data.p95.toFixed(2),
|
|
719
|
+
isVisible: () => true,
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
key: "p99",
|
|
723
|
+
label: "P99 (ms)",
|
|
724
|
+
format: (data) => data.p99.toFixed(2),
|
|
725
|
+
isVisible: () => true,
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
key: "avgWaitTime",
|
|
729
|
+
label: "Avg Wait (ms)",
|
|
730
|
+
format: (data) => data.avgWaitTime.toFixed(2),
|
|
731
|
+
isVisible: () => true,
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
key: "minWaitTime",
|
|
735
|
+
label: "Min Wait (ms)",
|
|
736
|
+
format: (data) => data.minWaitTime.toFixed(2),
|
|
737
|
+
isVisible: () => true,
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
key: "maxWaitTime",
|
|
741
|
+
label: "Max Wait (ms)",
|
|
742
|
+
format: (data) => data.maxWaitTime.toFixed(2),
|
|
743
|
+
isVisible: () => true,
|
|
744
|
+
},
|
|
745
|
+
];
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Column configuration for risk management markdown reports.
|
|
749
|
+
*
|
|
750
|
+
* Defines the table structure for displaying risk rejection events in risk management reports.
|
|
751
|
+
* Each column specifies how to format and display signals that were rejected due to risk limits.
|
|
752
|
+
*
|
|
753
|
+
* Used by {@link RiskMarkdownService} to generate markdown tables showing:
|
|
754
|
+
* - Signal identification (symbol, strategy name, signal ID, position)
|
|
755
|
+
* - Exchange information (exchange name, active position count)
|
|
756
|
+
* - Price data (open price, take profit, stop loss, current price)
|
|
757
|
+
* - Rejection details (rejection reason/comment, timestamp)
|
|
758
|
+
*
|
|
759
|
+
* @remarks
|
|
760
|
+
* This configuration helps analyze when and why the risk management system rejected signals.
|
|
761
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
762
|
+
* Useful for tuning risk parameters and understanding risk control effectiveness.
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
* ```typescript
|
|
766
|
+
* import { risk_columns } from "./assets/risk.columns";
|
|
767
|
+
*
|
|
768
|
+
* // Use with RiskMarkdownService
|
|
769
|
+
* const service = new RiskMarkdownService();
|
|
770
|
+
* await service.getReport("BTCUSDT", "my-strategy", risk_columns);
|
|
771
|
+
*
|
|
772
|
+
* // Or customize to focus on rejection reasons
|
|
773
|
+
* const customColumns = risk_columns.filter(col =>
|
|
774
|
+
* ["symbol", "strategyName", "comment", "activePositionCount", "timestamp"].includes(col.key)
|
|
775
|
+
* );
|
|
776
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
777
|
+
* ```
|
|
778
|
+
*
|
|
779
|
+
* @see {@link RiskMarkdownService} for usage in report generation
|
|
780
|
+
* @see {@link ColumnModel} for column interface definition
|
|
781
|
+
* @see {@link RiskEvent} for data structure
|
|
782
|
+
*/
|
|
783
|
+
const risk_columns = [
|
|
784
|
+
{
|
|
785
|
+
key: "symbol",
|
|
786
|
+
label: "Symbol",
|
|
787
|
+
format: (data) => data.symbol,
|
|
788
|
+
isVisible: () => true,
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
key: "strategyName",
|
|
792
|
+
label: "Strategy",
|
|
793
|
+
format: (data) => data.strategyName,
|
|
794
|
+
isVisible: () => true,
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
key: "signalId",
|
|
798
|
+
label: "Signal ID",
|
|
799
|
+
format: (data) => data.pendingSignal.id || "N/A",
|
|
800
|
+
isVisible: () => true,
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
key: "position",
|
|
804
|
+
label: "Position",
|
|
805
|
+
format: (data) => data.pendingSignal.position.toUpperCase(),
|
|
806
|
+
isVisible: () => true,
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
key: "note",
|
|
810
|
+
label: "Note",
|
|
811
|
+
format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
|
|
812
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
key: "exchangeName",
|
|
816
|
+
label: "Exchange",
|
|
817
|
+
format: (data) => data.exchangeName,
|
|
818
|
+
isVisible: () => true,
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
key: "openPrice",
|
|
822
|
+
label: "Open Price",
|
|
823
|
+
format: (data) => data.pendingSignal.priceOpen !== undefined
|
|
824
|
+
? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
|
|
825
|
+
: "N/A",
|
|
826
|
+
isVisible: () => true,
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
key: "takeProfit",
|
|
830
|
+
label: "Take Profit",
|
|
831
|
+
format: (data) => data.pendingSignal.priceTakeProfit !== undefined
|
|
832
|
+
? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
|
|
833
|
+
: "N/A",
|
|
834
|
+
isVisible: () => true,
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
key: "stopLoss",
|
|
838
|
+
label: "Stop Loss",
|
|
839
|
+
format: (data) => data.pendingSignal.priceStopLoss !== undefined
|
|
840
|
+
? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
|
|
841
|
+
: "N/A",
|
|
842
|
+
isVisible: () => true,
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
key: "currentPrice",
|
|
846
|
+
label: "Current Price",
|
|
847
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
848
|
+
isVisible: () => true,
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
key: "activePositionCount",
|
|
852
|
+
label: "Active Positions",
|
|
853
|
+
format: (data) => data.activePositionCount.toString(),
|
|
854
|
+
isVisible: () => true,
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
key: "comment",
|
|
858
|
+
label: "Reason",
|
|
859
|
+
format: (data) => data.comment,
|
|
860
|
+
isVisible: () => true,
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
key: "timestamp",
|
|
864
|
+
label: "Timestamp",
|
|
865
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
866
|
+
isVisible: () => true,
|
|
867
|
+
},
|
|
868
|
+
];
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Column configuration for scheduled signals markdown reports.
|
|
872
|
+
*
|
|
873
|
+
* Defines the table structure for displaying scheduled, opened, and cancelled signal events.
|
|
874
|
+
* Each column specifies how to format and display signal scheduling and activation data.
|
|
875
|
+
*
|
|
876
|
+
* Used by {@link ScheduleMarkdownService} to generate markdown tables showing:
|
|
877
|
+
* - Event information (timestamp, action: scheduled/opened/cancelled)
|
|
878
|
+
* - Signal identification (symbol, signal ID, position)
|
|
879
|
+
* - Price data (current price, entry price, take profit, stop loss)
|
|
880
|
+
* - Timing information (wait time in minutes before activation or cancellation)
|
|
881
|
+
*
|
|
882
|
+
* @remarks
|
|
883
|
+
* This configuration tracks the lifecycle of scheduled signals from creation to activation or cancellation.
|
|
884
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
885
|
+
* Helps analyze signal scheduling effectiveness and cancellation patterns.
|
|
886
|
+
*
|
|
887
|
+
* @example
|
|
888
|
+
* ```typescript
|
|
889
|
+
* import { schedule_columns } from "./assets/schedule.columns";
|
|
890
|
+
*
|
|
891
|
+
* // Use with ScheduleMarkdownService
|
|
892
|
+
* const service = new ScheduleMarkdownService();
|
|
893
|
+
* await service.getReport("BTCUSDT", "my-strategy", schedule_columns);
|
|
894
|
+
*
|
|
895
|
+
* // Or customize for timing analysis
|
|
896
|
+
* const customColumns = schedule_columns.filter(col =>
|
|
897
|
+
* ["timestamp", "action", "symbol", "duration"].includes(col.key)
|
|
898
|
+
* );
|
|
899
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
900
|
+
* ```
|
|
901
|
+
*
|
|
902
|
+
* @see {@link ScheduleMarkdownService} for usage in report generation
|
|
903
|
+
* @see {@link ColumnModel} for column interface definition
|
|
904
|
+
* @see {@link ScheduledEvent} for data structure
|
|
905
|
+
*/
|
|
906
|
+
const schedule_columns = [
|
|
907
|
+
{
|
|
908
|
+
key: "timestamp",
|
|
909
|
+
label: "Timestamp",
|
|
910
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
911
|
+
isVisible: () => true,
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
key: "action",
|
|
915
|
+
label: "Action",
|
|
916
|
+
format: (data) => data.action.toUpperCase(),
|
|
917
|
+
isVisible: () => true,
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
key: "symbol",
|
|
921
|
+
label: "Symbol",
|
|
922
|
+
format: (data) => data.symbol,
|
|
923
|
+
isVisible: () => true,
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
key: "signalId",
|
|
927
|
+
label: "Signal ID",
|
|
928
|
+
format: (data) => data.signalId,
|
|
929
|
+
isVisible: () => true,
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
key: "position",
|
|
933
|
+
label: "Position",
|
|
934
|
+
format: (data) => data.position.toUpperCase(),
|
|
935
|
+
isVisible: () => true,
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
key: "note",
|
|
939
|
+
label: "Note",
|
|
940
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
941
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
key: "currentPrice",
|
|
945
|
+
label: "Current Price",
|
|
946
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
947
|
+
isVisible: () => true,
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
key: "priceOpen",
|
|
951
|
+
label: "Entry Price",
|
|
952
|
+
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
953
|
+
isVisible: () => true,
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
key: "takeProfit",
|
|
957
|
+
label: "Take Profit",
|
|
958
|
+
format: (data) => `${data.takeProfit.toFixed(8)} USD`,
|
|
959
|
+
isVisible: () => true,
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
key: "stopLoss",
|
|
963
|
+
label: "Stop Loss",
|
|
964
|
+
format: (data) => `${data.stopLoss.toFixed(8)} USD`,
|
|
965
|
+
isVisible: () => true,
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
key: "duration",
|
|
969
|
+
label: "Wait Time (min)",
|
|
970
|
+
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
971
|
+
isVisible: () => true,
|
|
972
|
+
},
|
|
973
|
+
];
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Column configuration for walker strategy comparison table in markdown reports.
|
|
977
|
+
*
|
|
978
|
+
* Defines the table structure for displaying strategy comparison results in walker backtest reports.
|
|
979
|
+
* Each column specifies how to format and display aggregated strategy performance metrics.
|
|
980
|
+
*
|
|
981
|
+
* Used by {@link WalkerMarkdownService} to generate markdown tables showing:
|
|
982
|
+
* - Strategy ranking and identification
|
|
983
|
+
* - Optimization metric value (generic "Metric" column)
|
|
984
|
+
* - Performance statistics (Total Signals, Win Rate, Average PNL, Total PNL)
|
|
985
|
+
* - Risk metrics (Sharpe Ratio, Standard Deviation)
|
|
986
|
+
*
|
|
987
|
+
* @remarks
|
|
988
|
+
* This configuration is used in walker reports to compare multiple strategy configurations.
|
|
989
|
+
* The "Metric" column displays the value of the metric being optimized (Sharpe, PNL, etc.).
|
|
990
|
+
* Strategies are automatically sorted by metric value (best performers first).
|
|
991
|
+
*
|
|
992
|
+
* @example
|
|
993
|
+
* ```typescript
|
|
994
|
+
* import { walker_strategy_columns } from "./assets/walker.columns";
|
|
995
|
+
*
|
|
996
|
+
* // Use with WalkerMarkdownService for strategy comparison
|
|
997
|
+
* const service = new WalkerMarkdownService();
|
|
998
|
+
* await service.getReport(
|
|
999
|
+
* "my-walker",
|
|
1000
|
+
* "BTCUSDT",
|
|
1001
|
+
* "sharpeRatio",
|
|
1002
|
+
* { exchangeName: "binance", frameName: "1d" },
|
|
1003
|
+
* walker_strategy_columns
|
|
1004
|
+
* );
|
|
1005
|
+
* ```
|
|
1006
|
+
*
|
|
1007
|
+
* @see {@link WalkerMarkdownService} for usage in report generation
|
|
1008
|
+
* @see {@link ColumnModel} for column interface definition
|
|
1009
|
+
* @see {@link IStrategyResult} for data structure
|
|
1010
|
+
*/
|
|
1011
|
+
const walker_strategy_columns = [
|
|
1012
|
+
{
|
|
1013
|
+
key: "rank",
|
|
1014
|
+
label: "Rank",
|
|
1015
|
+
format: (_data, index) => `${index + 1}`,
|
|
1016
|
+
isVisible: () => true,
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
key: "strategy",
|
|
1020
|
+
label: "Strategy",
|
|
1021
|
+
format: (data) => data.strategyName,
|
|
1022
|
+
isVisible: () => true,
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
key: "metric",
|
|
1026
|
+
label: "Metric",
|
|
1027
|
+
format: (data) => data.metricValue !== null ? data.metricValue.toFixed(2) : "N/A",
|
|
1028
|
+
isVisible: () => true,
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
key: "totalSignals",
|
|
1032
|
+
label: "Total Signals",
|
|
1033
|
+
format: (data) => `${data.stats.totalSignals}`,
|
|
1034
|
+
isVisible: () => true,
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
key: "winRate",
|
|
1038
|
+
label: "Win Rate",
|
|
1039
|
+
format: (data) => data.stats.winRate !== null
|
|
1040
|
+
? `${data.stats.winRate.toFixed(2)}%`
|
|
1041
|
+
: "N/A",
|
|
1042
|
+
isVisible: () => true,
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
key: "avgPnl",
|
|
1046
|
+
label: "Avg PNL",
|
|
1047
|
+
format: (data) => data.stats.avgPnl !== null
|
|
1048
|
+
? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
|
|
1049
|
+
: "N/A",
|
|
1050
|
+
isVisible: () => true,
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
key: "totalPnl",
|
|
1054
|
+
label: "Total PNL",
|
|
1055
|
+
format: (data) => data.stats.totalPnl !== null
|
|
1056
|
+
? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
|
|
1057
|
+
: "N/A",
|
|
1058
|
+
isVisible: () => true,
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
key: "sharpeRatio",
|
|
1062
|
+
label: "Sharpe Ratio",
|
|
1063
|
+
format: (data) => data.stats.sharpeRatio !== null
|
|
1064
|
+
? `${data.stats.sharpeRatio.toFixed(3)}`
|
|
1065
|
+
: "N/A",
|
|
1066
|
+
isVisible: () => true,
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
key: "stdDev",
|
|
1070
|
+
label: "Std Dev",
|
|
1071
|
+
format: (data) => data.stats.stdDev !== null
|
|
1072
|
+
? `${data.stats.stdDev.toFixed(3)}%`
|
|
1073
|
+
: "N/A",
|
|
1074
|
+
isVisible: () => true,
|
|
1075
|
+
},
|
|
1076
|
+
];
|
|
1077
|
+
/**
|
|
1078
|
+
* Column configuration for walker PNL table in markdown reports.
|
|
1079
|
+
*
|
|
1080
|
+
* Defines the table structure for displaying all closed signals across all strategies in walker backtest reports.
|
|
1081
|
+
* Each column specifies how to format and display individual signal trade data.
|
|
1082
|
+
*
|
|
1083
|
+
* Used by {@link WalkerMarkdownService} to generate markdown tables showing:
|
|
1084
|
+
* - Strategy identification for each signal
|
|
1085
|
+
* - Signal details (signal ID, symbol, position)
|
|
1086
|
+
* - Trade performance (PNL percentage, close reason)
|
|
1087
|
+
* - Timing information (open time, close time)
|
|
1088
|
+
*
|
|
1089
|
+
* @remarks
|
|
1090
|
+
* This configuration aggregates all signals from all tested strategies into a single comprehensive table.
|
|
1091
|
+
* Useful for detailed analysis of individual trades across different strategy configurations.
|
|
1092
|
+
*
|
|
1093
|
+
* @example
|
|
1094
|
+
* ```typescript
|
|
1095
|
+
* import { walker_pnl_columns } from "./assets/walker.columns";
|
|
1096
|
+
*
|
|
1097
|
+
* // Use with WalkerMarkdownService for signal-level analysis
|
|
1098
|
+
* const service = new WalkerMarkdownService();
|
|
1099
|
+
* await service.getReport(
|
|
1100
|
+
* "my-walker",
|
|
1101
|
+
* "BTCUSDT",
|
|
1102
|
+
* "sharpeRatio",
|
|
1103
|
+
* { exchangeName: "binance", frameName: "1d" },
|
|
1104
|
+
* undefined, // use default strategy columns
|
|
1105
|
+
* walker_pnl_columns
|
|
1106
|
+
* );
|
|
1107
|
+
* ```
|
|
1108
|
+
*
|
|
1109
|
+
* @see {@link WalkerMarkdownService} for usage in report generation
|
|
1110
|
+
* @see {@link ColumnModel} for column interface definition
|
|
1111
|
+
* @see {@link SignalData} for data structure
|
|
1112
|
+
*/
|
|
1113
|
+
const walker_pnl_columns = [
|
|
1114
|
+
{
|
|
1115
|
+
key: "strategy",
|
|
1116
|
+
label: "Strategy",
|
|
1117
|
+
format: (data) => data.strategyName,
|
|
1118
|
+
isVisible: () => true,
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
key: "signalId",
|
|
1122
|
+
label: "Signal ID",
|
|
1123
|
+
format: (data) => data.signalId,
|
|
1124
|
+
isVisible: () => true,
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
key: "symbol",
|
|
1128
|
+
label: "Symbol",
|
|
1129
|
+
format: (data) => data.symbol,
|
|
1130
|
+
isVisible: () => true,
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
key: "position",
|
|
1134
|
+
label: "Position",
|
|
1135
|
+
format: (data) => data.position.toUpperCase(),
|
|
1136
|
+
isVisible: () => true,
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
key: "pnl",
|
|
1140
|
+
label: "PNL (net)",
|
|
1141
|
+
format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
|
|
1142
|
+
isVisible: () => true,
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
key: "closeReason",
|
|
1146
|
+
label: "Close Reason",
|
|
1147
|
+
format: (data) => data.closeReason,
|
|
1148
|
+
isVisible: () => true,
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
key: "openTime",
|
|
1152
|
+
label: "Open Time",
|
|
1153
|
+
format: (data) => new Date(data.openTime).toISOString(),
|
|
1154
|
+
isVisible: () => true,
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
key: "closeTime",
|
|
1158
|
+
label: "Close Time",
|
|
1159
|
+
format: (data) => new Date(data.closeTime).toISOString(),
|
|
1160
|
+
isVisible: () => true,
|
|
1161
|
+
},
|
|
1162
|
+
];
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Mapping of available table/markdown reports to their column definitions.
|
|
1166
|
+
*
|
|
1167
|
+
* Each property references a column definition object imported from
|
|
1168
|
+
* `src/assets/*.columns`. These are used by markdown/report generators
|
|
1169
|
+
* (backtest, live, schedule, risk, heat, performance, partial, walker).
|
|
1170
|
+
*/
|
|
1171
|
+
const COLUMN_CONFIG = {
|
|
1172
|
+
/** Columns used in backtest markdown tables and reports */
|
|
1173
|
+
backtest_columns,
|
|
1174
|
+
/** Columns used by heatmap / heat reports */
|
|
1175
|
+
heat_columns,
|
|
1176
|
+
/** Columns for live trading reports and logs */
|
|
1177
|
+
live_columns,
|
|
1178
|
+
/** Columns for partial-results / incremental reports */
|
|
1179
|
+
partial_columns,
|
|
1180
|
+
/** Columns for performance summary reports */
|
|
1181
|
+
performance_columns,
|
|
1182
|
+
/** Columns for risk-related reports */
|
|
1183
|
+
risk_columns,
|
|
1184
|
+
/** Columns for scheduled report output */
|
|
1185
|
+
schedule_columns,
|
|
1186
|
+
/** Walker: PnL summary columns */
|
|
1187
|
+
walker_pnl_columns,
|
|
1188
|
+
/** Walker: strategy-level summary columns */
|
|
1189
|
+
walker_strategy_columns,
|
|
1190
|
+
};
|
|
1191
|
+
/**
|
|
1192
|
+
* Immutable default columns mapping used across the application.
|
|
1193
|
+
* Use `DEFAULT_COLUMNS` when you need a read-only reference to the
|
|
1194
|
+
* canonical column configuration.
|
|
1195
|
+
*/
|
|
1196
|
+
const DEFAULT_COLUMNS = Object.freeze({ ...COLUMN_CONFIG });
|
|
1197
|
+
|
|
123
1198
|
const { init, inject, provide } = createActivator("backtest");
|
|
124
1199
|
|
|
125
1200
|
/**
|
|
@@ -219,6 +1294,7 @@ const validationServices$1 = {
|
|
|
219
1294
|
riskValidationService: Symbol('riskValidationService'),
|
|
220
1295
|
optimizerValidationService: Symbol('optimizerValidationService'),
|
|
221
1296
|
configValidationService: Symbol('configValidationService'),
|
|
1297
|
+
columnValidationService: Symbol('columnValidationService'),
|
|
222
1298
|
};
|
|
223
1299
|
const templateServices$1 = {
|
|
224
1300
|
optimizerTemplateService: Symbol('optimizerTemplateService'),
|
|
@@ -1550,7 +2626,7 @@ const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
|
1550
2626
|
* Utility class for managing partial profit/loss levels persistence.
|
|
1551
2627
|
*
|
|
1552
2628
|
* Features:
|
|
1553
|
-
* - Memoized storage instances per symbol
|
|
2629
|
+
* - Memoized storage instances per symbol:strategyName
|
|
1554
2630
|
* - Custom adapter support
|
|
1555
2631
|
* - Atomic read/write operations for partial data
|
|
1556
2632
|
* - Crash-safe partial state management
|
|
@@ -1560,23 +2636,25 @@ const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
|
1560
2636
|
class PersistPartialUtils {
|
|
1561
2637
|
constructor() {
|
|
1562
2638
|
this.PersistPartialFactory = PersistBase;
|
|
1563
|
-
this.getPartialStorage = memoize(([symbol]) => `${symbol}`, (symbol) => Reflect.construct(this.PersistPartialFactory, [
|
|
1564
|
-
symbol
|
|
2639
|
+
this.getPartialStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => Reflect.construct(this.PersistPartialFactory, [
|
|
2640
|
+
`${symbol}_${strategyName}`,
|
|
1565
2641
|
`./dump/data/partial/`,
|
|
1566
2642
|
]));
|
|
1567
2643
|
/**
|
|
1568
|
-
* Reads persisted partial data for a symbol.
|
|
2644
|
+
* Reads persisted partial data for a symbol and strategy.
|
|
1569
2645
|
*
|
|
1570
2646
|
* Called by ClientPartial.waitForInit() to restore state.
|
|
1571
2647
|
* Returns empty object if no partial data exists.
|
|
1572
2648
|
*
|
|
1573
2649
|
* @param symbol - Trading pair symbol
|
|
2650
|
+
* @param strategyName - Strategy identifier
|
|
1574
2651
|
* @returns Promise resolving to partial data record
|
|
1575
2652
|
*/
|
|
1576
|
-
this.readPartialData = async (symbol) => {
|
|
2653
|
+
this.readPartialData = async (symbol, strategyName) => {
|
|
1577
2654
|
backtest$1.loggerService.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
2655
|
+
const key = `${symbol}:${strategyName}`;
|
|
2656
|
+
const isInitial = !this.getPartialStorage.has(key);
|
|
2657
|
+
const stateStorage = this.getPartialStorage(symbol, strategyName);
|
|
1580
2658
|
await stateStorage.waitForInit(isInitial);
|
|
1581
2659
|
const PARTIAL_STORAGE_KEY = "levels";
|
|
1582
2660
|
if (await stateStorage.hasValue(PARTIAL_STORAGE_KEY)) {
|
|
@@ -1592,12 +2670,14 @@ class PersistPartialUtils {
|
|
|
1592
2670
|
*
|
|
1593
2671
|
* @param partialData - Record of signal IDs to partial data
|
|
1594
2672
|
* @param symbol - Trading pair symbol
|
|
2673
|
+
* @param strategyName - Strategy identifier
|
|
1595
2674
|
* @returns Promise that resolves when write is complete
|
|
1596
2675
|
*/
|
|
1597
|
-
this.writePartialData = async (partialData, symbol) => {
|
|
2676
|
+
this.writePartialData = async (partialData, symbol, strategyName) => {
|
|
1598
2677
|
backtest$1.loggerService.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1599
|
-
const
|
|
1600
|
-
const
|
|
2678
|
+
const key = `${symbol}:${strategyName}`;
|
|
2679
|
+
const isInitial = !this.getPartialStorage.has(key);
|
|
2680
|
+
const stateStorage = this.getPartialStorage(symbol, strategyName);
|
|
1601
2681
|
await stateStorage.waitForInit(isInitial);
|
|
1602
2682
|
const PARTIAL_STORAGE_KEY = "levels";
|
|
1603
2683
|
await stateStorage.writeValue(PARTIAL_STORAGE_KEY, partialData);
|
|
@@ -1632,10 +2712,10 @@ class PersistPartialUtils {
|
|
|
1632
2712
|
* PersistPartialAdapter.usePersistPartialAdapter(RedisPersist);
|
|
1633
2713
|
*
|
|
1634
2714
|
* // Read partial data
|
|
1635
|
-
* const partialData = await PersistPartialAdapter.readPartialData("BTCUSDT");
|
|
2715
|
+
* const partialData = await PersistPartialAdapter.readPartialData("BTCUSDT", "my-strategy");
|
|
1636
2716
|
*
|
|
1637
2717
|
* // Write partial data
|
|
1638
|
-
* await PersistPartialAdapter.writePartialData(partialData, "BTCUSDT");
|
|
2718
|
+
* await PersistPartialAdapter.writePartialData(partialData, "BTCUSDT", "my-strategy");
|
|
1639
2719
|
* ```
|
|
1640
2720
|
*/
|
|
1641
2721
|
const PersistPartialAdapter = new PersistPartialUtils();
|
|
@@ -1763,53 +2843,6 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
1763
2843
|
walkerStopSubject: walkerStopSubject
|
|
1764
2844
|
});
|
|
1765
2845
|
|
|
1766
|
-
/**
|
|
1767
|
-
* Converts markdown content to plain text with minimal formatting
|
|
1768
|
-
* @param content - Markdown string to convert
|
|
1769
|
-
* @returns Plain text representation
|
|
1770
|
-
*/
|
|
1771
|
-
const toPlainString = (content) => {
|
|
1772
|
-
if (!content) {
|
|
1773
|
-
return "";
|
|
1774
|
-
}
|
|
1775
|
-
let text = content;
|
|
1776
|
-
// Remove code blocks
|
|
1777
|
-
text = text.replace(/```[\s\S]*?```/g, "");
|
|
1778
|
-
text = text.replace(/`([^`]+)`/g, "$1");
|
|
1779
|
-
// Remove images
|
|
1780
|
-
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
1781
|
-
// Convert links to text only (keep link text, remove URL)
|
|
1782
|
-
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
1783
|
-
// Remove headers (convert to plain text)
|
|
1784
|
-
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
1785
|
-
// Remove bold and italic markers
|
|
1786
|
-
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
1787
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
1788
|
-
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
1789
|
-
text = text.replace(/___(.+?)___/g, "$1");
|
|
1790
|
-
text = text.replace(/__(.+?)__/g, "$1");
|
|
1791
|
-
text = text.replace(/_(.+?)_/g, "$1");
|
|
1792
|
-
// Remove strikethrough
|
|
1793
|
-
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
1794
|
-
// Convert lists to plain text with bullets
|
|
1795
|
-
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
1796
|
-
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
1797
|
-
// Remove blockquotes
|
|
1798
|
-
text = text.replace(/^\s*>\s+/gm, "");
|
|
1799
|
-
// Remove horizontal rules
|
|
1800
|
-
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
1801
|
-
// Remove HTML tags
|
|
1802
|
-
text = text.replace(/<[^>]+>/g, "");
|
|
1803
|
-
// Remove excessive whitespace and normalize line breaks
|
|
1804
|
-
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
1805
|
-
text = text.replace(/[ \t]+/g, " ");
|
|
1806
|
-
// Remove all newline characters
|
|
1807
|
-
text = text.replace(/\n/g, " ");
|
|
1808
|
-
// Remove excessive spaces after newline removal
|
|
1809
|
-
text = text.replace(/\s+/g, " ");
|
|
1810
|
-
return text.trim();
|
|
1811
|
-
};
|
|
1812
|
-
|
|
1813
2846
|
const INTERVAL_MINUTES$1 = {
|
|
1814
2847
|
"1m": 1,
|
|
1815
2848
|
"3m": 3,
|
|
@@ -3388,7 +4421,7 @@ class RiskUtils {
|
|
|
3388
4421
|
*
|
|
3389
4422
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3390
4423
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
3391
|
-
* @returns Promise resolving to
|
|
4424
|
+
* @returns Promise resolving to RiskStatisticsModel object with counts and event list
|
|
3392
4425
|
*
|
|
3393
4426
|
* @example
|
|
3394
4427
|
* ```typescript
|
|
@@ -3435,6 +4468,7 @@ class RiskUtils {
|
|
|
3435
4468
|
*
|
|
3436
4469
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3437
4470
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
4471
|
+
* @param columns - Optional columns configuration for the report
|
|
3438
4472
|
* @returns Promise resolving to markdown formatted report string
|
|
3439
4473
|
*
|
|
3440
4474
|
* @example
|
|
@@ -3458,7 +4492,7 @@ class RiskUtils {
|
|
|
3458
4492
|
* // - my-strategy: 1
|
|
3459
4493
|
* ```
|
|
3460
4494
|
*/
|
|
3461
|
-
this.getReport = async (symbol, strategyName) => {
|
|
4495
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
3462
4496
|
backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, {
|
|
3463
4497
|
symbol,
|
|
3464
4498
|
strategyName,
|
|
@@ -3470,7 +4504,7 @@ class RiskUtils {
|
|
|
3470
4504
|
backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
|
|
3471
4505
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT));
|
|
3472
4506
|
}
|
|
3473
|
-
return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
|
|
4507
|
+
return await backtest$1.riskMarkdownService.getReport(symbol, strategyName, columns);
|
|
3474
4508
|
};
|
|
3475
4509
|
/**
|
|
3476
4510
|
* Generates and saves markdown report to file.
|
|
@@ -3487,6 +4521,7 @@ class RiskUtils {
|
|
|
3487
4521
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3488
4522
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
3489
4523
|
* @param path - Output directory path (default: "./dump/risk")
|
|
4524
|
+
* @param columns - Optional columns configuration for the report
|
|
3490
4525
|
* @returns Promise that resolves when file is written
|
|
3491
4526
|
*
|
|
3492
4527
|
* @example
|
|
@@ -3503,7 +4538,7 @@ class RiskUtils {
|
|
|
3503
4538
|
* }
|
|
3504
4539
|
* ```
|
|
3505
4540
|
*/
|
|
3506
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
4541
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
3507
4542
|
backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, {
|
|
3508
4543
|
symbol,
|
|
3509
4544
|
strategyName,
|
|
@@ -3516,7 +4551,7 @@ class RiskUtils {
|
|
|
3516
4551
|
backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
|
|
3517
4552
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP));
|
|
3518
4553
|
}
|
|
3519
|
-
await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
|
|
4554
|
+
await backtest$1.riskMarkdownService.dump(symbol, strategyName, path, columns);
|
|
3520
4555
|
};
|
|
3521
4556
|
}
|
|
3522
4557
|
}
|
|
@@ -3541,25 +4576,25 @@ const NOOP_RISK = {
|
|
|
3541
4576
|
addSignal: () => Promise.resolve(),
|
|
3542
4577
|
removeSignal: () => Promise.resolve(),
|
|
3543
4578
|
};
|
|
3544
|
-
const GET_RISK_FN = (dto, self) => {
|
|
4579
|
+
const GET_RISK_FN = (dto, backtest, self) => {
|
|
3545
4580
|
const hasRiskName = !!dto.riskName;
|
|
3546
|
-
const hasRiskList = !!
|
|
4581
|
+
const hasRiskList = !!dto.riskList?.length;
|
|
3547
4582
|
// Нет ни riskName, ни riskList
|
|
3548
4583
|
if (!hasRiskName && !hasRiskList) {
|
|
3549
4584
|
return NOOP_RISK;
|
|
3550
4585
|
}
|
|
3551
4586
|
// Есть только riskName (без riskList)
|
|
3552
4587
|
if (hasRiskName && !hasRiskList) {
|
|
3553
|
-
return self.riskConnectionService.getRisk(dto.riskName);
|
|
4588
|
+
return self.riskConnectionService.getRisk(dto.riskName, backtest);
|
|
3554
4589
|
}
|
|
3555
4590
|
// Есть только riskList (без riskName)
|
|
3556
4591
|
if (!hasRiskName && hasRiskList) {
|
|
3557
|
-
return new MergeRisk(dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName)));
|
|
4592
|
+
return new MergeRisk(dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName, backtest)));
|
|
3558
4593
|
}
|
|
3559
4594
|
// Есть и riskName, и riskList - объединяем (riskName в начало)
|
|
3560
4595
|
return new MergeRisk([
|
|
3561
|
-
self.riskConnectionService.getRisk(dto.riskName),
|
|
3562
|
-
...dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName))
|
|
4596
|
+
self.riskConnectionService.getRisk(dto.riskName, backtest),
|
|
4597
|
+
...dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName, backtest)),
|
|
3563
4598
|
]);
|
|
3564
4599
|
};
|
|
3565
4600
|
/**
|
|
@@ -3601,7 +4636,7 @@ class StrategyConnectionService {
|
|
|
3601
4636
|
* @param strategyName - Name of registered strategy schema
|
|
3602
4637
|
* @returns Configured ClientStrategy instance
|
|
3603
4638
|
*/
|
|
3604
|
-
this.getStrategy = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => {
|
|
4639
|
+
this.getStrategy = memoize(([symbol, strategyName, backtest]) => `${symbol}:${strategyName}:${backtest ? "backtest" : "live"}`, (symbol, strategyName, backtest) => {
|
|
3605
4640
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
3606
4641
|
return new ClientStrategy({
|
|
3607
4642
|
symbol,
|
|
@@ -3614,7 +4649,7 @@ class StrategyConnectionService {
|
|
|
3614
4649
|
risk: GET_RISK_FN({
|
|
3615
4650
|
riskName,
|
|
3616
4651
|
riskList,
|
|
3617
|
-
}, this),
|
|
4652
|
+
}, backtest, this),
|
|
3618
4653
|
riskName,
|
|
3619
4654
|
strategyName,
|
|
3620
4655
|
getSignal,
|
|
@@ -3631,12 +4666,13 @@ class StrategyConnectionService {
|
|
|
3631
4666
|
*
|
|
3632
4667
|
* @returns Promise resolving to pending signal or null
|
|
3633
4668
|
*/
|
|
3634
|
-
this.getPendingSignal = async (symbol, strategyName) => {
|
|
4669
|
+
this.getPendingSignal = async (backtest, symbol, strategyName) => {
|
|
3635
4670
|
this.loggerService.log("strategyConnectionService getPendingSignal", {
|
|
3636
4671
|
symbol,
|
|
3637
4672
|
strategyName,
|
|
4673
|
+
backtest,
|
|
3638
4674
|
});
|
|
3639
|
-
const strategy = this.getStrategy(symbol, strategyName);
|
|
4675
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3640
4676
|
return await strategy.getPendingSignal(symbol, strategyName);
|
|
3641
4677
|
};
|
|
3642
4678
|
/**
|
|
@@ -3649,12 +4685,13 @@ class StrategyConnectionService {
|
|
|
3649
4685
|
* @param strategyName - Name of the strategy
|
|
3650
4686
|
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
3651
4687
|
*/
|
|
3652
|
-
this.getStopped = async (symbol, strategyName) => {
|
|
4688
|
+
this.getStopped = async (backtest, symbol, strategyName) => {
|
|
3653
4689
|
this.loggerService.log("strategyConnectionService getStopped", {
|
|
3654
4690
|
symbol,
|
|
3655
4691
|
strategyName,
|
|
4692
|
+
backtest,
|
|
3656
4693
|
});
|
|
3657
|
-
const strategy = this.getStrategy(symbol, strategyName);
|
|
4694
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3658
4695
|
return await strategy.getStopped(symbol, strategyName);
|
|
3659
4696
|
};
|
|
3660
4697
|
/**
|
|
@@ -3672,7 +4709,8 @@ class StrategyConnectionService {
|
|
|
3672
4709
|
symbol,
|
|
3673
4710
|
strategyName,
|
|
3674
4711
|
});
|
|
3675
|
-
const
|
|
4712
|
+
const backtest = this.executionContextService.context.backtest;
|
|
4713
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3676
4714
|
await strategy.waitForInit();
|
|
3677
4715
|
const tick = await strategy.tick(symbol, strategyName);
|
|
3678
4716
|
{
|
|
@@ -3703,7 +4741,8 @@ class StrategyConnectionService {
|
|
|
3703
4741
|
strategyName,
|
|
3704
4742
|
candleCount: candles.length,
|
|
3705
4743
|
});
|
|
3706
|
-
const
|
|
4744
|
+
const backtest = this.executionContextService.context.backtest;
|
|
4745
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3707
4746
|
await strategy.waitForInit();
|
|
3708
4747
|
const tick = await strategy.backtest(symbol, strategyName, candles);
|
|
3709
4748
|
{
|
|
@@ -3724,11 +4763,11 @@ class StrategyConnectionService {
|
|
|
3724
4763
|
* @param strategyName - Name of strategy to stop
|
|
3725
4764
|
* @returns Promise that resolves when stop flag is set
|
|
3726
4765
|
*/
|
|
3727
|
-
this.stop = async (
|
|
4766
|
+
this.stop = async (backtest, ctx) => {
|
|
3728
4767
|
this.loggerService.log("strategyConnectionService stop", {
|
|
3729
4768
|
ctx,
|
|
3730
4769
|
});
|
|
3731
|
-
const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
|
|
4770
|
+
const strategy = this.getStrategy(ctx.symbol, ctx.strategyName, backtest);
|
|
3732
4771
|
await strategy.stop(ctx.symbol, ctx.strategyName, backtest);
|
|
3733
4772
|
};
|
|
3734
4773
|
/**
|
|
@@ -3739,12 +4778,12 @@ class StrategyConnectionService {
|
|
|
3739
4778
|
*
|
|
3740
4779
|
* @param ctx - Optional context with symbol and strategyName (clears all if not provided)
|
|
3741
4780
|
*/
|
|
3742
|
-
this.clear = async (ctx) => {
|
|
4781
|
+
this.clear = async (backtest, ctx) => {
|
|
3743
4782
|
this.loggerService.log("strategyConnectionService clear", {
|
|
3744
4783
|
ctx,
|
|
3745
4784
|
});
|
|
3746
4785
|
if (ctx) {
|
|
3747
|
-
const key = `${ctx.symbol}:${ctx.strategyName}`;
|
|
4786
|
+
const key = `${ctx.symbol}:${ctx.strategyName}:${backtest ? "backtest" : "live"}`;
|
|
3748
4787
|
this.getStrategy.clear(key);
|
|
3749
4788
|
}
|
|
3750
4789
|
else {
|
|
@@ -4139,9 +5178,15 @@ const DO_VALIDATION_FN = trycatch(async (validation, params) => {
|
|
|
4139
5178
|
* Initializes active positions by reading from persistence.
|
|
4140
5179
|
* Uses singleshot pattern to ensure it only runs once.
|
|
4141
5180
|
* This function is exported for use in tests or other modules.
|
|
5181
|
+
*
|
|
5182
|
+
* In backtest mode, initializes with empty Map. In live mode, reads from persist storage.
|
|
4142
5183
|
*/
|
|
4143
5184
|
const WAIT_FOR_INIT_FN$1 = async (self) => {
|
|
4144
|
-
self.params.logger.debug("ClientRisk waitForInit");
|
|
5185
|
+
self.params.logger.debug("ClientRisk waitForInit", { backtest: self.params.backtest });
|
|
5186
|
+
if (self.params.backtest) {
|
|
5187
|
+
self._activePositions = new Map();
|
|
5188
|
+
return;
|
|
5189
|
+
}
|
|
4145
5190
|
const persistedPositions = await PersistRiskAdapter.readPositionData(self.params.riskName);
|
|
4146
5191
|
self._activePositions = new Map(persistedPositions);
|
|
4147
5192
|
};
|
|
@@ -4189,6 +5234,7 @@ class ClientRisk {
|
|
|
4189
5234
|
this.params.logger.debug("ClientRisk checkSignal", {
|
|
4190
5235
|
symbol: params.symbol,
|
|
4191
5236
|
strategyName: params.strategyName,
|
|
5237
|
+
backtest: this.params.backtest,
|
|
4192
5238
|
});
|
|
4193
5239
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4194
5240
|
await this.waitForInit();
|
|
@@ -4234,8 +5280,12 @@ class ClientRisk {
|
|
|
4234
5280
|
}
|
|
4235
5281
|
/**
|
|
4236
5282
|
* Persists current active positions to disk.
|
|
5283
|
+
* Skips in backtest mode.
|
|
4237
5284
|
*/
|
|
4238
5285
|
async _updatePositions() {
|
|
5286
|
+
if (this.params.backtest) {
|
|
5287
|
+
return;
|
|
5288
|
+
}
|
|
4239
5289
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4240
5290
|
await this.waitForInit();
|
|
4241
5291
|
}
|
|
@@ -4249,6 +5299,7 @@ class ClientRisk {
|
|
|
4249
5299
|
this.params.logger.debug("ClientRisk addSignal", {
|
|
4250
5300
|
symbol,
|
|
4251
5301
|
context,
|
|
5302
|
+
backtest: this.params.backtest,
|
|
4252
5303
|
});
|
|
4253
5304
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4254
5305
|
await this.waitForInit();
|
|
@@ -4271,6 +5322,7 @@ class ClientRisk {
|
|
|
4271
5322
|
this.params.logger.debug("ClientRisk removeSignal", {
|
|
4272
5323
|
symbol,
|
|
4273
5324
|
context,
|
|
5325
|
+
backtest: this.params.backtest,
|
|
4274
5326
|
});
|
|
4275
5327
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4276
5328
|
await this.waitForInit();
|
|
@@ -4341,19 +5393,21 @@ class RiskConnectionService {
|
|
|
4341
5393
|
this.loggerService = inject(TYPES.loggerService);
|
|
4342
5394
|
this.riskSchemaService = inject(TYPES.riskSchemaService);
|
|
4343
5395
|
/**
|
|
4344
|
-
* Retrieves memoized ClientRisk instance for given risk name.
|
|
5396
|
+
* Retrieves memoized ClientRisk instance for given risk name and backtest mode.
|
|
4345
5397
|
*
|
|
4346
5398
|
* Creates ClientRisk on first call, returns cached instance on subsequent calls.
|
|
4347
|
-
* Cache key is riskName string.
|
|
5399
|
+
* Cache key is "riskName:backtest" string to separate live and backtest instances.
|
|
4348
5400
|
*
|
|
4349
5401
|
* @param riskName - Name of registered risk schema
|
|
5402
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
4350
5403
|
* @returns Configured ClientRisk instance
|
|
4351
5404
|
*/
|
|
4352
|
-
this.getRisk = memoize(([riskName]) => `${riskName}`, (riskName) => {
|
|
5405
|
+
this.getRisk = memoize(([riskName, backtest]) => `${riskName}:${backtest ? "backtest" : "live"}`, (riskName, backtest) => {
|
|
4353
5406
|
const schema = this.riskSchemaService.get(riskName);
|
|
4354
5407
|
return new ClientRisk({
|
|
4355
5408
|
...schema,
|
|
4356
5409
|
logger: this.loggerService,
|
|
5410
|
+
backtest,
|
|
4357
5411
|
onRejected: COMMIT_REJECTION_FN,
|
|
4358
5412
|
});
|
|
4359
5413
|
});
|
|
@@ -4365,7 +5419,7 @@ class RiskConnectionService {
|
|
|
4365
5419
|
* ClientRisk will emit riskSubject event via onRejected callback when signal is rejected.
|
|
4366
5420
|
*
|
|
4367
5421
|
* @param params - Risk check arguments (portfolio state, position details)
|
|
4368
|
-
* @param context - Execution context with risk name
|
|
5422
|
+
* @param context - Execution context with risk name and backtest mode
|
|
4369
5423
|
* @returns Promise resolving to risk check result
|
|
4370
5424
|
*/
|
|
4371
5425
|
this.checkSignal = async (params, context) => {
|
|
@@ -4373,46 +5427,48 @@ class RiskConnectionService {
|
|
|
4373
5427
|
symbol: params.symbol,
|
|
4374
5428
|
context,
|
|
4375
5429
|
});
|
|
4376
|
-
return await this.getRisk(context.riskName).checkSignal(params);
|
|
5430
|
+
return await this.getRisk(context.riskName, context.backtest).checkSignal(params);
|
|
4377
5431
|
};
|
|
4378
5432
|
/**
|
|
4379
5433
|
* Registers an opened signal with the risk management system.
|
|
4380
5434
|
* Routes to appropriate ClientRisk instance.
|
|
4381
5435
|
*
|
|
4382
5436
|
* @param symbol - Trading pair symbol
|
|
4383
|
-
* @param context - Context information (strategyName, riskName)
|
|
5437
|
+
* @param context - Context information (strategyName, riskName, backtest)
|
|
4384
5438
|
*/
|
|
4385
5439
|
this.addSignal = async (symbol, context) => {
|
|
4386
5440
|
this.loggerService.log("riskConnectionService addSignal", {
|
|
4387
5441
|
symbol,
|
|
4388
5442
|
context,
|
|
4389
5443
|
});
|
|
4390
|
-
await this.getRisk(context.riskName).addSignal(symbol, context);
|
|
5444
|
+
await this.getRisk(context.riskName, context.backtest).addSignal(symbol, context);
|
|
4391
5445
|
};
|
|
4392
5446
|
/**
|
|
4393
5447
|
* Removes a closed signal from the risk management system.
|
|
4394
5448
|
* Routes to appropriate ClientRisk instance.
|
|
4395
5449
|
*
|
|
4396
5450
|
* @param symbol - Trading pair symbol
|
|
4397
|
-
* @param context - Context information (strategyName, riskName)
|
|
5451
|
+
* @param context - Context information (strategyName, riskName, backtest)
|
|
4398
5452
|
*/
|
|
4399
5453
|
this.removeSignal = async (symbol, context) => {
|
|
4400
5454
|
this.loggerService.log("riskConnectionService removeSignal", {
|
|
4401
5455
|
symbol,
|
|
4402
5456
|
context,
|
|
4403
5457
|
});
|
|
4404
|
-
await this.getRisk(context.riskName).removeSignal(symbol, context);
|
|
5458
|
+
await this.getRisk(context.riskName, context.backtest).removeSignal(symbol, context);
|
|
4405
5459
|
};
|
|
4406
5460
|
/**
|
|
4407
5461
|
* Clears the cached ClientRisk instance for the given risk name.
|
|
4408
5462
|
*
|
|
4409
5463
|
* @param riskName - Name of the risk schema to clear from cache
|
|
4410
5464
|
*/
|
|
4411
|
-
this.clear = async (riskName) => {
|
|
5465
|
+
this.clear = async (backtest, riskName) => {
|
|
4412
5466
|
this.loggerService.log("riskConnectionService clear", {
|
|
4413
5467
|
riskName,
|
|
5468
|
+
backtest,
|
|
4414
5469
|
});
|
|
4415
|
-
|
|
5470
|
+
const key = `${riskName}:${backtest ? "backtest" : "live"}`;
|
|
5471
|
+
this.getRisk.clear(key);
|
|
4416
5472
|
};
|
|
4417
5473
|
}
|
|
4418
5474
|
}
|
|
@@ -4635,7 +5691,7 @@ class StrategyCoreService {
|
|
|
4635
5691
|
* @param strategyName - Name of the strategy
|
|
4636
5692
|
* @returns Promise resolving to pending signal or null
|
|
4637
5693
|
*/
|
|
4638
|
-
this.getPendingSignal = async (symbol, strategyName) => {
|
|
5694
|
+
this.getPendingSignal = async (backtest, symbol, strategyName) => {
|
|
4639
5695
|
this.loggerService.log("strategyCoreService getPendingSignal", {
|
|
4640
5696
|
symbol,
|
|
4641
5697
|
strategyName,
|
|
@@ -4644,7 +5700,7 @@ class StrategyCoreService {
|
|
|
4644
5700
|
throw new Error("strategyCoreService getPendingSignal requires a method context");
|
|
4645
5701
|
}
|
|
4646
5702
|
await this.validate(symbol, strategyName);
|
|
4647
|
-
return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
|
|
5703
|
+
return await this.strategyConnectionService.getPendingSignal(backtest, symbol, strategyName);
|
|
4648
5704
|
};
|
|
4649
5705
|
/**
|
|
4650
5706
|
* Checks if the strategy has been stopped.
|
|
@@ -4656,16 +5712,17 @@ class StrategyCoreService {
|
|
|
4656
5712
|
* @param strategyName - Name of the strategy
|
|
4657
5713
|
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
4658
5714
|
*/
|
|
4659
|
-
this.getStopped = async (symbol, strategyName) => {
|
|
5715
|
+
this.getStopped = async (backtest, symbol, strategyName) => {
|
|
4660
5716
|
this.loggerService.log("strategyCoreService getStopped", {
|
|
4661
5717
|
symbol,
|
|
4662
5718
|
strategyName,
|
|
5719
|
+
backtest,
|
|
4663
5720
|
});
|
|
4664
5721
|
if (!MethodContextService.hasContext()) {
|
|
4665
5722
|
throw new Error("strategyCoreService getStopped requires a method context");
|
|
4666
5723
|
}
|
|
4667
5724
|
await this.validate(symbol, strategyName);
|
|
4668
|
-
return await this.strategyConnectionService.getStopped(symbol, strategyName);
|
|
5725
|
+
return await this.strategyConnectionService.getStopped(backtest, symbol, strategyName);
|
|
4669
5726
|
};
|
|
4670
5727
|
/**
|
|
4671
5728
|
* Checks signal status at a specific timestamp.
|
|
@@ -4739,13 +5796,13 @@ class StrategyCoreService {
|
|
|
4739
5796
|
* @param strategyName - Name of strategy to stop
|
|
4740
5797
|
* @returns Promise that resolves when stop flag is set
|
|
4741
5798
|
*/
|
|
4742
|
-
this.stop = async (
|
|
5799
|
+
this.stop = async (backtest, ctx) => {
|
|
4743
5800
|
this.loggerService.log("strategyCoreService stop", {
|
|
4744
5801
|
ctx,
|
|
4745
5802
|
backtest,
|
|
4746
5803
|
});
|
|
4747
5804
|
await this.validate(ctx.symbol, ctx.strategyName);
|
|
4748
|
-
return await this.strategyConnectionService.stop(
|
|
5805
|
+
return await this.strategyConnectionService.stop(backtest, ctx);
|
|
4749
5806
|
};
|
|
4750
5807
|
/**
|
|
4751
5808
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -4755,14 +5812,14 @@ class StrategyCoreService {
|
|
|
4755
5812
|
*
|
|
4756
5813
|
* @param ctx - Optional context with symbol and strategyName (clears all if not provided)
|
|
4757
5814
|
*/
|
|
4758
|
-
this.clear = async (ctx) => {
|
|
5815
|
+
this.clear = async (backtest, ctx) => {
|
|
4759
5816
|
this.loggerService.log("strategyCoreService clear", {
|
|
4760
5817
|
ctx,
|
|
4761
5818
|
});
|
|
4762
5819
|
if (ctx) {
|
|
4763
5820
|
await this.validate(ctx.symbol, ctx.strategyName);
|
|
4764
5821
|
}
|
|
4765
|
-
return await this.strategyConnectionService.clear(ctx);
|
|
5822
|
+
return await this.strategyConnectionService.clear(backtest, ctx);
|
|
4766
5823
|
};
|
|
4767
5824
|
}
|
|
4768
5825
|
}
|
|
@@ -4903,14 +5960,15 @@ class RiskGlobalService {
|
|
|
4903
5960
|
* If no riskName is provided, clears all risk data.
|
|
4904
5961
|
* @param riskName - Optional name of the risk instance to clear
|
|
4905
5962
|
*/
|
|
4906
|
-
this.clear = async (riskName) => {
|
|
5963
|
+
this.clear = async (backtest, riskName) => {
|
|
4907
5964
|
this.loggerService.log("riskGlobalService clear", {
|
|
4908
5965
|
riskName,
|
|
5966
|
+
backtest,
|
|
4909
5967
|
});
|
|
4910
5968
|
if (riskName) {
|
|
4911
5969
|
await this.validate(riskName);
|
|
4912
5970
|
}
|
|
4913
|
-
return await this.riskConnectionService.clear(riskName);
|
|
5971
|
+
return await this.riskConnectionService.clear(backtest, riskName);
|
|
4914
5972
|
};
|
|
4915
5973
|
}
|
|
4916
5974
|
}
|
|
@@ -5033,6 +6091,19 @@ class StrategySchemaService {
|
|
|
5033
6091
|
if (typeof strategySchema.strategyName !== "string") {
|
|
5034
6092
|
throw new Error(`strategy schema validation failed: missing strategyName`);
|
|
5035
6093
|
}
|
|
6094
|
+
if (strategySchema.riskName && typeof strategySchema.riskName !== "string") {
|
|
6095
|
+
throw new Error(`strategy schema validation failed: invalid riskName`);
|
|
6096
|
+
}
|
|
6097
|
+
if (strategySchema.riskList && !Array.isArray(strategySchema.riskList)) {
|
|
6098
|
+
throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} system=${strategySchema.riskList}`);
|
|
6099
|
+
}
|
|
6100
|
+
if (strategySchema.riskList &&
|
|
6101
|
+
strategySchema.riskList.length !== new Set(strategySchema.riskList).size) {
|
|
6102
|
+
throw new Error(`strategy schema validation failed: found duplicate riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
|
|
6103
|
+
}
|
|
6104
|
+
if (strategySchema.riskList?.some((value) => typeof value !== "string")) {
|
|
6105
|
+
throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
|
|
6106
|
+
}
|
|
5036
6107
|
if (typeof strategySchema.interval !== "string") {
|
|
5037
6108
|
throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
|
|
5038
6109
|
}
|
|
@@ -5441,7 +6512,7 @@ class BacktestLogicPrivateService {
|
|
|
5441
6512
|
});
|
|
5442
6513
|
}
|
|
5443
6514
|
// Check if strategy should stop before processing next frame
|
|
5444
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6515
|
+
if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
|
|
5445
6516
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
|
|
5446
6517
|
symbol,
|
|
5447
6518
|
when: when.toISOString(),
|
|
@@ -5466,7 +6537,7 @@ class BacktestLogicPrivateService {
|
|
|
5466
6537
|
continue;
|
|
5467
6538
|
}
|
|
5468
6539
|
// Check if strategy should stop when idle (no active signal)
|
|
5469
|
-
if (await and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
6540
|
+
if (await and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName))) {
|
|
5470
6541
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
|
|
5471
6542
|
symbol,
|
|
5472
6543
|
when: when.toISOString(),
|
|
@@ -5568,7 +6639,7 @@ class BacktestLogicPrivateService {
|
|
|
5568
6639
|
}
|
|
5569
6640
|
yield backtestResult;
|
|
5570
6641
|
// Check if strategy should stop after signal is closed
|
|
5571
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6642
|
+
if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
|
|
5572
6643
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
|
|
5573
6644
|
symbol,
|
|
5574
6645
|
signalId: backtestResult.signal.id,
|
|
@@ -5661,7 +6732,7 @@ class BacktestLogicPrivateService {
|
|
|
5661
6732
|
}
|
|
5662
6733
|
yield backtestResult;
|
|
5663
6734
|
// Check if strategy should stop after signal is closed
|
|
5664
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6735
|
+
if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
|
|
5665
6736
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
|
|
5666
6737
|
symbol,
|
|
5667
6738
|
signalId: backtestResult.signal.id,
|
|
@@ -5804,7 +6875,7 @@ class LiveLogicPrivateService {
|
|
|
5804
6875
|
previousEventTimestamp = currentTimestamp;
|
|
5805
6876
|
// Check if strategy should stop when idle (no active signal)
|
|
5806
6877
|
if (result.action === "idle") {
|
|
5807
|
-
if (await and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
6878
|
+
if (await and(Promise.resolve(true), this.strategyCoreService.getStopped(false, symbol, this.methodContextService.context.strategyName))) {
|
|
5808
6879
|
this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
|
|
5809
6880
|
symbol,
|
|
5810
6881
|
when: when.toISOString(),
|
|
@@ -5826,7 +6897,7 @@ class LiveLogicPrivateService {
|
|
|
5826
6897
|
yield result;
|
|
5827
6898
|
// Check if strategy should stop after signal is closed
|
|
5828
6899
|
if (result.action === "closed") {
|
|
5829
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6900
|
+
if (await this.strategyCoreService.getStopped(false, symbol, this.methodContextService.context.strategyName)) {
|
|
5830
6901
|
this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
|
|
5831
6902
|
symbol,
|
|
5832
6903
|
signalId: result.signal.id,
|
|
@@ -6274,167 +7345,80 @@ class BacktestCommandService {
|
|
|
6274
7345
|
}
|
|
6275
7346
|
{
|
|
6276
7347
|
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
6277
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
|
|
6278
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
|
|
6279
|
-
}
|
|
6280
|
-
return this.backtestLogicPublicService.run(symbol, context);
|
|
6281
|
-
};
|
|
6282
|
-
}
|
|
6283
|
-
}
|
|
6284
|
-
|
|
6285
|
-
const METHOD_NAME_RUN = "walkerCommandService run";
|
|
6286
|
-
/**
|
|
6287
|
-
* Global service providing access to walker functionality.
|
|
6288
|
-
*
|
|
6289
|
-
* Simple wrapper around WalkerLogicPublicService for dependency injection.
|
|
6290
|
-
* Used by public API exports.
|
|
6291
|
-
*/
|
|
6292
|
-
class WalkerCommandService {
|
|
6293
|
-
constructor() {
|
|
6294
|
-
this.loggerService = inject(TYPES.loggerService);
|
|
6295
|
-
this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
|
|
6296
|
-
this.walkerSchemaService = inject(TYPES.walkerSchemaService);
|
|
6297
|
-
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
6298
|
-
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
6299
|
-
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
6300
|
-
this.walkerValidationService = inject(TYPES.walkerValidationService);
|
|
6301
|
-
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
6302
|
-
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
6303
|
-
/**
|
|
6304
|
-
* Runs walker comparison for a symbol with context propagation.
|
|
6305
|
-
*
|
|
6306
|
-
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
6307
|
-
* @param context - Walker context with strategies and metric
|
|
6308
|
-
*/
|
|
6309
|
-
this.run = (symbol, context) => {
|
|
6310
|
-
this.loggerService.log(METHOD_NAME_RUN, {
|
|
6311
|
-
symbol,
|
|
6312
|
-
context,
|
|
6313
|
-
});
|
|
6314
|
-
{
|
|
6315
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
|
|
6316
|
-
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
|
|
6317
|
-
this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
|
|
6318
|
-
}
|
|
6319
|
-
{
|
|
6320
|
-
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
6321
|
-
for (const strategyName of walkerSchema.strategies) {
|
|
6322
|
-
const { riskName, riskList } = this.strategySchemaService.get(strategyName);
|
|
6323
|
-
this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
|
|
6324
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
|
|
6325
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
|
|
6326
|
-
}
|
|
6327
|
-
}
|
|
6328
|
-
return this.walkerLogicPublicService.run(symbol, context);
|
|
6329
|
-
};
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
|
|
6333
|
-
/**
|
|
6334
|
-
* Checks if a value is unsafe for display (not a number, NaN, or Infinity).
|
|
6335
|
-
*
|
|
6336
|
-
* @param value - Value to check
|
|
6337
|
-
* @returns true if value is unsafe, false otherwise
|
|
6338
|
-
*/
|
|
6339
|
-
function isUnsafe$3(value) {
|
|
6340
|
-
if (typeof value !== "number") {
|
|
6341
|
-
return true;
|
|
6342
|
-
}
|
|
6343
|
-
if (isNaN(value)) {
|
|
6344
|
-
return true;
|
|
6345
|
-
}
|
|
6346
|
-
if (!isFinite(value)) {
|
|
6347
|
-
return true;
|
|
6348
|
-
}
|
|
6349
|
-
return false;
|
|
6350
|
-
}
|
|
6351
|
-
const columns$6 = [
|
|
6352
|
-
{
|
|
6353
|
-
key: "signalId",
|
|
6354
|
-
label: "Signal ID",
|
|
6355
|
-
format: (data) => data.signal.id,
|
|
6356
|
-
isVisible: () => true,
|
|
6357
|
-
},
|
|
6358
|
-
{
|
|
6359
|
-
key: "symbol",
|
|
6360
|
-
label: "Symbol",
|
|
6361
|
-
format: (data) => data.signal.symbol,
|
|
6362
|
-
isVisible: () => true,
|
|
6363
|
-
},
|
|
6364
|
-
{
|
|
6365
|
-
key: "position",
|
|
6366
|
-
label: "Position",
|
|
6367
|
-
format: (data) => data.signal.position.toUpperCase(),
|
|
6368
|
-
isVisible: () => true,
|
|
6369
|
-
},
|
|
6370
|
-
{
|
|
6371
|
-
key: "note",
|
|
6372
|
-
label: "Note",
|
|
6373
|
-
format: (data) => toPlainString(data.signal.note ?? "N/A"),
|
|
6374
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
6375
|
-
},
|
|
6376
|
-
{
|
|
6377
|
-
key: "openPrice",
|
|
6378
|
-
label: "Open Price",
|
|
6379
|
-
format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
|
|
6380
|
-
isVisible: () => true,
|
|
6381
|
-
},
|
|
6382
|
-
{
|
|
6383
|
-
key: "closePrice",
|
|
6384
|
-
label: "Close Price",
|
|
6385
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
6386
|
-
isVisible: () => true,
|
|
6387
|
-
},
|
|
6388
|
-
{
|
|
6389
|
-
key: "takeProfit",
|
|
6390
|
-
label: "Take Profit",
|
|
6391
|
-
format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
|
|
6392
|
-
isVisible: () => true,
|
|
6393
|
-
},
|
|
6394
|
-
{
|
|
6395
|
-
key: "stopLoss",
|
|
6396
|
-
label: "Stop Loss",
|
|
6397
|
-
format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
|
|
6398
|
-
isVisible: () => true,
|
|
6399
|
-
},
|
|
6400
|
-
{
|
|
6401
|
-
key: "pnl",
|
|
6402
|
-
label: "PNL (net)",
|
|
6403
|
-
format: (data) => {
|
|
6404
|
-
const pnlPercentage = data.pnl.pnlPercentage;
|
|
6405
|
-
return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
|
|
6406
|
-
},
|
|
6407
|
-
isVisible: () => true,
|
|
6408
|
-
},
|
|
6409
|
-
{
|
|
6410
|
-
key: "closeReason",
|
|
6411
|
-
label: "Close Reason",
|
|
6412
|
-
format: (data) => data.closeReason,
|
|
6413
|
-
isVisible: () => true,
|
|
6414
|
-
},
|
|
6415
|
-
{
|
|
6416
|
-
key: "duration",
|
|
6417
|
-
label: "Duration (min)",
|
|
6418
|
-
format: (data) => {
|
|
6419
|
-
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
6420
|
-
const durationMin = Math.round(durationMs / 60000);
|
|
6421
|
-
return `${durationMin}`;
|
|
6422
|
-
},
|
|
6423
|
-
isVisible: () => true,
|
|
6424
|
-
},
|
|
6425
|
-
{
|
|
6426
|
-
key: "openTimestamp",
|
|
6427
|
-
label: "Open Time",
|
|
6428
|
-
format: (data) => new Date(data.signal.pendingAt).toISOString(),
|
|
6429
|
-
isVisible: () => true,
|
|
6430
|
-
},
|
|
6431
|
-
{
|
|
6432
|
-
key: "closeTimestamp",
|
|
6433
|
-
label: "Close Time",
|
|
6434
|
-
format: (data) => new Date(data.closeTimestamp).toISOString(),
|
|
6435
|
-
isVisible: () => true,
|
|
6436
|
-
},
|
|
6437
|
-
];
|
|
7348
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
|
|
7349
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
|
|
7350
|
+
}
|
|
7351
|
+
return this.backtestLogicPublicService.run(symbol, context);
|
|
7352
|
+
};
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
|
|
7356
|
+
const METHOD_NAME_RUN = "walkerCommandService run";
|
|
7357
|
+
/**
|
|
7358
|
+
* Global service providing access to walker functionality.
|
|
7359
|
+
*
|
|
7360
|
+
* Simple wrapper around WalkerLogicPublicService for dependency injection.
|
|
7361
|
+
* Used by public API exports.
|
|
7362
|
+
*/
|
|
7363
|
+
class WalkerCommandService {
|
|
7364
|
+
constructor() {
|
|
7365
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
7366
|
+
this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
|
|
7367
|
+
this.walkerSchemaService = inject(TYPES.walkerSchemaService);
|
|
7368
|
+
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
7369
|
+
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
7370
|
+
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
7371
|
+
this.walkerValidationService = inject(TYPES.walkerValidationService);
|
|
7372
|
+
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
7373
|
+
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
7374
|
+
/**
|
|
7375
|
+
* Runs walker comparison for a symbol with context propagation.
|
|
7376
|
+
*
|
|
7377
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
7378
|
+
* @param context - Walker context with strategies and metric
|
|
7379
|
+
*/
|
|
7380
|
+
this.run = (symbol, context) => {
|
|
7381
|
+
this.loggerService.log(METHOD_NAME_RUN, {
|
|
7382
|
+
symbol,
|
|
7383
|
+
context,
|
|
7384
|
+
});
|
|
7385
|
+
{
|
|
7386
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
|
|
7387
|
+
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
|
|
7388
|
+
this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
|
|
7389
|
+
}
|
|
7390
|
+
{
|
|
7391
|
+
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
7392
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
7393
|
+
const { riskName, riskList } = this.strategySchemaService.get(strategyName);
|
|
7394
|
+
this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
|
|
7395
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
|
|
7396
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
|
|
7397
|
+
}
|
|
7398
|
+
}
|
|
7399
|
+
return this.walkerLogicPublicService.run(symbol, context);
|
|
7400
|
+
};
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
|
|
7404
|
+
/**
|
|
7405
|
+
* Checks if a value is unsafe for display (not a number, NaN, or Infinity).
|
|
7406
|
+
*
|
|
7407
|
+
* @param value - Value to check
|
|
7408
|
+
* @returns true if value is unsafe, false otherwise
|
|
7409
|
+
*/
|
|
7410
|
+
function isUnsafe$3(value) {
|
|
7411
|
+
if (typeof value !== "number") {
|
|
7412
|
+
return true;
|
|
7413
|
+
}
|
|
7414
|
+
if (isNaN(value)) {
|
|
7415
|
+
return true;
|
|
7416
|
+
}
|
|
7417
|
+
if (!isFinite(value)) {
|
|
7418
|
+
return true;
|
|
7419
|
+
}
|
|
7420
|
+
return false;
|
|
7421
|
+
}
|
|
6438
7422
|
/** Maximum number of signals to store in backtest reports */
|
|
6439
7423
|
const MAX_EVENTS$6 = 250;
|
|
6440
7424
|
/**
|
|
@@ -6528,9 +7512,10 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6528
7512
|
* Generates markdown report with all closed signals for a strategy (View).
|
|
6529
7513
|
*
|
|
6530
7514
|
* @param strategyName - Strategy name
|
|
7515
|
+
* @param columns - Column configuration for formatting the table
|
|
6531
7516
|
* @returns Markdown formatted report with all signals
|
|
6532
7517
|
*/
|
|
6533
|
-
async getReport(strategyName) {
|
|
7518
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.backtest_columns) {
|
|
6534
7519
|
const stats = await this.getData();
|
|
6535
7520
|
if (stats.totalSignals === 0) {
|
|
6536
7521
|
return [
|
|
@@ -6539,10 +7524,15 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6539
7524
|
"No signals closed yet."
|
|
6540
7525
|
].join("\n");
|
|
6541
7526
|
}
|
|
6542
|
-
const visibleColumns =
|
|
7527
|
+
const visibleColumns = [];
|
|
7528
|
+
for (const col of columns) {
|
|
7529
|
+
if (await col.isVisible()) {
|
|
7530
|
+
visibleColumns.push(col);
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
6543
7533
|
const header = visibleColumns.map((col) => col.label);
|
|
6544
7534
|
const separator = visibleColumns.map(() => "---");
|
|
6545
|
-
const rows = this._signalList.map((closedSignal) => visibleColumns.map((col) => col.format(closedSignal)));
|
|
7535
|
+
const rows = await Promise.all(this._signalList.map(async (closedSignal, index) => Promise.all(visibleColumns.map((col) => col.format(closedSignal, index)))));
|
|
6546
7536
|
const tableData = [header, separator, ...rows];
|
|
6547
7537
|
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
6548
7538
|
return [
|
|
@@ -6567,9 +7557,10 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6567
7557
|
*
|
|
6568
7558
|
* @param strategyName - Strategy name
|
|
6569
7559
|
* @param path - Directory path to save report (default: "./dump/backtest")
|
|
7560
|
+
* @param columns - Column configuration for formatting the table
|
|
6570
7561
|
*/
|
|
6571
|
-
async dump(strategyName, path = "./dump/backtest") {
|
|
6572
|
-
const markdown = await this.getReport(strategyName);
|
|
7562
|
+
async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
7563
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
6573
7564
|
try {
|
|
6574
7565
|
const dir = join(process.cwd(), path);
|
|
6575
7566
|
await mkdir(dir, { recursive: true });
|
|
@@ -6677,6 +7668,7 @@ class BacktestMarkdownService {
|
|
|
6677
7668
|
*
|
|
6678
7669
|
* @param symbol - Trading pair symbol
|
|
6679
7670
|
* @param strategyName - Strategy name to generate report for
|
|
7671
|
+
* @param columns - Column configuration for formatting the table
|
|
6680
7672
|
* @returns Markdown formatted report string with table of all closed signals
|
|
6681
7673
|
*
|
|
6682
7674
|
* @example
|
|
@@ -6686,13 +7678,13 @@ class BacktestMarkdownService {
|
|
|
6686
7678
|
* console.log(markdown);
|
|
6687
7679
|
* ```
|
|
6688
7680
|
*/
|
|
6689
|
-
this.getReport = async (symbol, strategyName) => {
|
|
7681
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.backtest_columns) => {
|
|
6690
7682
|
this.loggerService.log("backtestMarkdownService getReport", {
|
|
6691
7683
|
symbol,
|
|
6692
7684
|
strategyName,
|
|
6693
7685
|
});
|
|
6694
7686
|
const storage = this.getStorage(symbol, strategyName);
|
|
6695
|
-
return storage.getReport(strategyName);
|
|
7687
|
+
return storage.getReport(strategyName, columns);
|
|
6696
7688
|
};
|
|
6697
7689
|
/**
|
|
6698
7690
|
* Saves symbol-strategy report to disk.
|
|
@@ -6702,6 +7694,7 @@ class BacktestMarkdownService {
|
|
|
6702
7694
|
* @param symbol - Trading pair symbol
|
|
6703
7695
|
* @param strategyName - Strategy name to save report for
|
|
6704
7696
|
* @param path - Directory path to save report (default: "./dump/backtest")
|
|
7697
|
+
* @param columns - Column configuration for formatting the table
|
|
6705
7698
|
*
|
|
6706
7699
|
* @example
|
|
6707
7700
|
* ```typescript
|
|
@@ -6714,14 +7707,14 @@ class BacktestMarkdownService {
|
|
|
6714
7707
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
6715
7708
|
* ```
|
|
6716
7709
|
*/
|
|
6717
|
-
this.dump = async (symbol, strategyName, path = "./dump/backtest") => {
|
|
7710
|
+
this.dump = async (symbol, strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) => {
|
|
6718
7711
|
this.loggerService.log("backtestMarkdownService dump", {
|
|
6719
7712
|
symbol,
|
|
6720
7713
|
strategyName,
|
|
6721
7714
|
path,
|
|
6722
7715
|
});
|
|
6723
7716
|
const storage = this.getStorage(symbol, strategyName);
|
|
6724
|
-
await storage.dump(strategyName, path);
|
|
7717
|
+
await storage.dump(strategyName, path, columns);
|
|
6725
7718
|
};
|
|
6726
7719
|
/**
|
|
6727
7720
|
* Clears accumulated signal data from storage.
|
|
@@ -6789,104 +7782,6 @@ function isUnsafe$2(value) {
|
|
|
6789
7782
|
}
|
|
6790
7783
|
return false;
|
|
6791
7784
|
}
|
|
6792
|
-
const columns$5 = [
|
|
6793
|
-
{
|
|
6794
|
-
key: "timestamp",
|
|
6795
|
-
label: "Timestamp",
|
|
6796
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
6797
|
-
isVisible: () => true,
|
|
6798
|
-
},
|
|
6799
|
-
{
|
|
6800
|
-
key: "action",
|
|
6801
|
-
label: "Action",
|
|
6802
|
-
format: (data) => data.action.toUpperCase(),
|
|
6803
|
-
isVisible: () => true,
|
|
6804
|
-
},
|
|
6805
|
-
{
|
|
6806
|
-
key: "symbol",
|
|
6807
|
-
label: "Symbol",
|
|
6808
|
-
format: (data) => data.symbol ?? "N/A",
|
|
6809
|
-
isVisible: () => true,
|
|
6810
|
-
},
|
|
6811
|
-
{
|
|
6812
|
-
key: "signalId",
|
|
6813
|
-
label: "Signal ID",
|
|
6814
|
-
format: (data) => data.signalId ?? "N/A",
|
|
6815
|
-
isVisible: () => true,
|
|
6816
|
-
},
|
|
6817
|
-
{
|
|
6818
|
-
key: "position",
|
|
6819
|
-
label: "Position",
|
|
6820
|
-
format: (data) => data.position?.toUpperCase() ?? "N/A",
|
|
6821
|
-
isVisible: () => true,
|
|
6822
|
-
},
|
|
6823
|
-
{
|
|
6824
|
-
key: "note",
|
|
6825
|
-
label: "Note",
|
|
6826
|
-
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
6827
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
6828
|
-
},
|
|
6829
|
-
{
|
|
6830
|
-
key: "currentPrice",
|
|
6831
|
-
label: "Current Price",
|
|
6832
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
6833
|
-
isVisible: () => true,
|
|
6834
|
-
},
|
|
6835
|
-
{
|
|
6836
|
-
key: "openPrice",
|
|
6837
|
-
label: "Open Price",
|
|
6838
|
-
format: (data) => data.openPrice !== undefined ? `${data.openPrice.toFixed(8)} USD` : "N/A",
|
|
6839
|
-
isVisible: () => true,
|
|
6840
|
-
},
|
|
6841
|
-
{
|
|
6842
|
-
key: "takeProfit",
|
|
6843
|
-
label: "Take Profit",
|
|
6844
|
-
format: (data) => data.takeProfit !== undefined
|
|
6845
|
-
? `${data.takeProfit.toFixed(8)} USD`
|
|
6846
|
-
: "N/A",
|
|
6847
|
-
isVisible: () => true,
|
|
6848
|
-
},
|
|
6849
|
-
{
|
|
6850
|
-
key: "stopLoss",
|
|
6851
|
-
label: "Stop Loss",
|
|
6852
|
-
format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
|
|
6853
|
-
isVisible: () => true,
|
|
6854
|
-
},
|
|
6855
|
-
{
|
|
6856
|
-
key: "percentTp",
|
|
6857
|
-
label: "% to TP",
|
|
6858
|
-
format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
|
|
6859
|
-
isVisible: () => true,
|
|
6860
|
-
},
|
|
6861
|
-
{
|
|
6862
|
-
key: "percentSl",
|
|
6863
|
-
label: "% to SL",
|
|
6864
|
-
format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
|
|
6865
|
-
isVisible: () => true,
|
|
6866
|
-
},
|
|
6867
|
-
{
|
|
6868
|
-
key: "pnl",
|
|
6869
|
-
label: "PNL (net)",
|
|
6870
|
-
format: (data) => {
|
|
6871
|
-
if (data.pnl === undefined)
|
|
6872
|
-
return "N/A";
|
|
6873
|
-
return `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`;
|
|
6874
|
-
},
|
|
6875
|
-
isVisible: () => true,
|
|
6876
|
-
},
|
|
6877
|
-
{
|
|
6878
|
-
key: "closeReason",
|
|
6879
|
-
label: "Close Reason",
|
|
6880
|
-
format: (data) => data.closeReason ?? "N/A",
|
|
6881
|
-
isVisible: () => true,
|
|
6882
|
-
},
|
|
6883
|
-
{
|
|
6884
|
-
key: "duration",
|
|
6885
|
-
label: "Duration (min)",
|
|
6886
|
-
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
6887
|
-
isVisible: () => true,
|
|
6888
|
-
},
|
|
6889
|
-
];
|
|
6890
7785
|
/** Maximum number of events to store in live trading reports */
|
|
6891
7786
|
const MAX_EVENTS$5 = 250;
|
|
6892
7787
|
/**
|
|
@@ -7098,9 +7993,10 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7098
7993
|
* Generates markdown report with all tick events for a strategy (View).
|
|
7099
7994
|
*
|
|
7100
7995
|
* @param strategyName - Strategy name
|
|
7996
|
+
* @param columns - Column configuration for formatting the table
|
|
7101
7997
|
* @returns Markdown formatted report with all events
|
|
7102
7998
|
*/
|
|
7103
|
-
async getReport(strategyName) {
|
|
7999
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.live_columns) {
|
|
7104
8000
|
const stats = await this.getData();
|
|
7105
8001
|
if (stats.totalEvents === 0) {
|
|
7106
8002
|
return [
|
|
@@ -7109,10 +8005,15 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7109
8005
|
"No events recorded yet."
|
|
7110
8006
|
].join("\n");
|
|
7111
8007
|
}
|
|
7112
|
-
const visibleColumns =
|
|
8008
|
+
const visibleColumns = [];
|
|
8009
|
+
for (const col of columns) {
|
|
8010
|
+
if (await col.isVisible()) {
|
|
8011
|
+
visibleColumns.push(col);
|
|
8012
|
+
}
|
|
8013
|
+
}
|
|
7113
8014
|
const header = visibleColumns.map((col) => col.label);
|
|
7114
8015
|
const separator = visibleColumns.map(() => "---");
|
|
7115
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
8016
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
7116
8017
|
const tableData = [header, separator, ...rows];
|
|
7117
8018
|
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
7118
8019
|
return [
|
|
@@ -7137,9 +8038,10 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7137
8038
|
*
|
|
7138
8039
|
* @param strategyName - Strategy name
|
|
7139
8040
|
* @param path - Directory path to save report (default: "./dump/live")
|
|
8041
|
+
* @param columns - Column configuration for formatting the table
|
|
7140
8042
|
*/
|
|
7141
|
-
async dump(strategyName, path = "./dump/live") {
|
|
7142
|
-
const markdown = await this.getReport(strategyName);
|
|
8043
|
+
async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
8044
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
7143
8045
|
try {
|
|
7144
8046
|
const dir = join(process.cwd(), path);
|
|
7145
8047
|
await mkdir(dir, { recursive: true });
|
|
@@ -7260,6 +8162,7 @@ class LiveMarkdownService {
|
|
|
7260
8162
|
*
|
|
7261
8163
|
* @param symbol - Trading pair symbol
|
|
7262
8164
|
* @param strategyName - Strategy name to generate report for
|
|
8165
|
+
* @param columns - Column configuration for formatting the table
|
|
7263
8166
|
* @returns Markdown formatted report string with table of all events
|
|
7264
8167
|
*
|
|
7265
8168
|
* @example
|
|
@@ -7269,13 +8172,13 @@ class LiveMarkdownService {
|
|
|
7269
8172
|
* console.log(markdown);
|
|
7270
8173
|
* ```
|
|
7271
8174
|
*/
|
|
7272
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8175
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.live_columns) => {
|
|
7273
8176
|
this.loggerService.log("liveMarkdownService getReport", {
|
|
7274
8177
|
symbol,
|
|
7275
8178
|
strategyName,
|
|
7276
8179
|
});
|
|
7277
8180
|
const storage = this.getStorage(symbol, strategyName);
|
|
7278
|
-
return storage.getReport(strategyName);
|
|
8181
|
+
return storage.getReport(strategyName, columns);
|
|
7279
8182
|
};
|
|
7280
8183
|
/**
|
|
7281
8184
|
* Saves symbol-strategy report to disk.
|
|
@@ -7285,6 +8188,7 @@ class LiveMarkdownService {
|
|
|
7285
8188
|
* @param symbol - Trading pair symbol
|
|
7286
8189
|
* @param strategyName - Strategy name to save report for
|
|
7287
8190
|
* @param path - Directory path to save report (default: "./dump/live")
|
|
8191
|
+
* @param columns - Column configuration for formatting the table
|
|
7288
8192
|
*
|
|
7289
8193
|
* @example
|
|
7290
8194
|
* ```typescript
|
|
@@ -7297,14 +8201,14 @@ class LiveMarkdownService {
|
|
|
7297
8201
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
7298
8202
|
* ```
|
|
7299
8203
|
*/
|
|
7300
|
-
this.dump = async (symbol, strategyName, path = "./dump/live") => {
|
|
8204
|
+
this.dump = async (symbol, strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) => {
|
|
7301
8205
|
this.loggerService.log("liveMarkdownService dump", {
|
|
7302
8206
|
symbol,
|
|
7303
8207
|
strategyName,
|
|
7304
8208
|
path,
|
|
7305
8209
|
});
|
|
7306
8210
|
const storage = this.getStorage(symbol, strategyName);
|
|
7307
|
-
await storage.dump(strategyName, path);
|
|
8211
|
+
await storage.dump(strategyName, path, columns);
|
|
7308
8212
|
};
|
|
7309
8213
|
/**
|
|
7310
8214
|
* Clears accumulated event data from storage.
|
|
@@ -7340,88 +8244,20 @@ class LiveMarkdownService {
|
|
|
7340
8244
|
* Initializes the service by subscribing to live signal events.
|
|
7341
8245
|
* Uses singleshot to ensure initialization happens only once.
|
|
7342
8246
|
* Automatically called on first use.
|
|
7343
|
-
*
|
|
7344
|
-
* @example
|
|
7345
|
-
* ```typescript
|
|
7346
|
-
* const service = new LiveMarkdownService();
|
|
7347
|
-
* await service.init(); // Subscribe to live events
|
|
7348
|
-
* ```
|
|
7349
|
-
*/
|
|
7350
|
-
this.init = singleshot(async () => {
|
|
7351
|
-
this.loggerService.log("liveMarkdownService init");
|
|
7352
|
-
signalLiveEmitter.subscribe(this.tick);
|
|
7353
|
-
});
|
|
7354
|
-
}
|
|
7355
|
-
}
|
|
7356
|
-
|
|
7357
|
-
const columns$4 = [
|
|
7358
|
-
{
|
|
7359
|
-
key: "timestamp",
|
|
7360
|
-
label: "Timestamp",
|
|
7361
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
7362
|
-
isVisible: () => true,
|
|
7363
|
-
},
|
|
7364
|
-
{
|
|
7365
|
-
key: "action",
|
|
7366
|
-
label: "Action",
|
|
7367
|
-
format: (data) => data.action.toUpperCase(),
|
|
7368
|
-
isVisible: () => true,
|
|
7369
|
-
},
|
|
7370
|
-
{
|
|
7371
|
-
key: "symbol",
|
|
7372
|
-
label: "Symbol",
|
|
7373
|
-
format: (data) => data.symbol,
|
|
7374
|
-
isVisible: () => true,
|
|
7375
|
-
},
|
|
7376
|
-
{
|
|
7377
|
-
key: "signalId",
|
|
7378
|
-
label: "Signal ID",
|
|
7379
|
-
format: (data) => data.signalId,
|
|
7380
|
-
isVisible: () => true,
|
|
7381
|
-
},
|
|
7382
|
-
{
|
|
7383
|
-
key: "position",
|
|
7384
|
-
label: "Position",
|
|
7385
|
-
format: (data) => data.position.toUpperCase(),
|
|
7386
|
-
isVisible: () => true,
|
|
7387
|
-
},
|
|
7388
|
-
{
|
|
7389
|
-
key: "note",
|
|
7390
|
-
label: "Note",
|
|
7391
|
-
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
7392
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
7393
|
-
},
|
|
7394
|
-
{
|
|
7395
|
-
key: "currentPrice",
|
|
7396
|
-
label: "Current Price",
|
|
7397
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
7398
|
-
isVisible: () => true,
|
|
7399
|
-
},
|
|
7400
|
-
{
|
|
7401
|
-
key: "priceOpen",
|
|
7402
|
-
label: "Entry Price",
|
|
7403
|
-
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
7404
|
-
isVisible: () => true,
|
|
7405
|
-
},
|
|
7406
|
-
{
|
|
7407
|
-
key: "takeProfit",
|
|
7408
|
-
label: "Take Profit",
|
|
7409
|
-
format: (data) => `${data.takeProfit.toFixed(8)} USD`,
|
|
7410
|
-
isVisible: () => true,
|
|
7411
|
-
},
|
|
7412
|
-
{
|
|
7413
|
-
key: "stopLoss",
|
|
7414
|
-
label: "Stop Loss",
|
|
7415
|
-
format: (data) => `${data.stopLoss.toFixed(8)} USD`,
|
|
7416
|
-
isVisible: () => true,
|
|
7417
|
-
},
|
|
7418
|
-
{
|
|
7419
|
-
key: "duration",
|
|
7420
|
-
label: "Wait Time (min)",
|
|
7421
|
-
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
7422
|
-
isVisible: () => true,
|
|
7423
|
-
},
|
|
7424
|
-
];
|
|
8247
|
+
*
|
|
8248
|
+
* @example
|
|
8249
|
+
* ```typescript
|
|
8250
|
+
* const service = new LiveMarkdownService();
|
|
8251
|
+
* await service.init(); // Subscribe to live events
|
|
8252
|
+
* ```
|
|
8253
|
+
*/
|
|
8254
|
+
this.init = singleshot(async () => {
|
|
8255
|
+
this.loggerService.log("liveMarkdownService init");
|
|
8256
|
+
signalLiveEmitter.subscribe(this.tick);
|
|
8257
|
+
});
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
8260
|
+
|
|
7425
8261
|
/** Maximum number of events to store in schedule reports */
|
|
7426
8262
|
const MAX_EVENTS$4 = 250;
|
|
7427
8263
|
/**
|
|
@@ -7566,9 +8402,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7566
8402
|
* Generates markdown report with all scheduled events for a strategy (View).
|
|
7567
8403
|
*
|
|
7568
8404
|
* @param strategyName - Strategy name
|
|
8405
|
+
* @param columns - Column configuration for formatting the table
|
|
7569
8406
|
* @returns Markdown formatted report with all events
|
|
7570
8407
|
*/
|
|
7571
|
-
async getReport(strategyName) {
|
|
8408
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.schedule_columns) {
|
|
7572
8409
|
const stats = await this.getData();
|
|
7573
8410
|
if (stats.totalEvents === 0) {
|
|
7574
8411
|
return [
|
|
@@ -7577,10 +8414,15 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7577
8414
|
"No scheduled signals recorded yet."
|
|
7578
8415
|
].join("\n");
|
|
7579
8416
|
}
|
|
7580
|
-
const visibleColumns =
|
|
8417
|
+
const visibleColumns = [];
|
|
8418
|
+
for (const col of columns) {
|
|
8419
|
+
if (await col.isVisible()) {
|
|
8420
|
+
visibleColumns.push(col);
|
|
8421
|
+
}
|
|
8422
|
+
}
|
|
7581
8423
|
const header = visibleColumns.map((col) => col.label);
|
|
7582
8424
|
const separator = visibleColumns.map(() => "---");
|
|
7583
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
8425
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
7584
8426
|
const tableData = [header, separator, ...rows];
|
|
7585
8427
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
7586
8428
|
return [
|
|
@@ -7603,9 +8445,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7603
8445
|
*
|
|
7604
8446
|
* @param strategyName - Strategy name
|
|
7605
8447
|
* @param path - Directory path to save report (default: "./dump/schedule")
|
|
8448
|
+
* @param columns - Column configuration for formatting the table
|
|
7606
8449
|
*/
|
|
7607
|
-
async dump(strategyName, path = "./dump/schedule") {
|
|
7608
|
-
const markdown = await this.getReport(strategyName);
|
|
8450
|
+
async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
8451
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
7609
8452
|
try {
|
|
7610
8453
|
const dir = join(process.cwd(), path);
|
|
7611
8454
|
await mkdir(dir, { recursive: true });
|
|
@@ -7711,6 +8554,7 @@ class ScheduleMarkdownService {
|
|
|
7711
8554
|
*
|
|
7712
8555
|
* @param symbol - Trading pair symbol
|
|
7713
8556
|
* @param strategyName - Strategy name to generate report for
|
|
8557
|
+
* @param columns - Column configuration for formatting the table
|
|
7714
8558
|
* @returns Markdown formatted report string with table of all events
|
|
7715
8559
|
*
|
|
7716
8560
|
* @example
|
|
@@ -7720,13 +8564,13 @@ class ScheduleMarkdownService {
|
|
|
7720
8564
|
* console.log(markdown);
|
|
7721
8565
|
* ```
|
|
7722
8566
|
*/
|
|
7723
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8567
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.schedule_columns) => {
|
|
7724
8568
|
this.loggerService.log("scheduleMarkdownService getReport", {
|
|
7725
8569
|
symbol,
|
|
7726
8570
|
strategyName,
|
|
7727
8571
|
});
|
|
7728
8572
|
const storage = this.getStorage(symbol, strategyName);
|
|
7729
|
-
return storage.getReport(strategyName);
|
|
8573
|
+
return storage.getReport(strategyName, columns);
|
|
7730
8574
|
};
|
|
7731
8575
|
/**
|
|
7732
8576
|
* Saves symbol-strategy report to disk.
|
|
@@ -7736,6 +8580,7 @@ class ScheduleMarkdownService {
|
|
|
7736
8580
|
* @param symbol - Trading pair symbol
|
|
7737
8581
|
* @param strategyName - Strategy name to save report for
|
|
7738
8582
|
* @param path - Directory path to save report (default: "./dump/schedule")
|
|
8583
|
+
* @param columns - Column configuration for formatting the table
|
|
7739
8584
|
*
|
|
7740
8585
|
* @example
|
|
7741
8586
|
* ```typescript
|
|
@@ -7748,14 +8593,14 @@ class ScheduleMarkdownService {
|
|
|
7748
8593
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
7749
8594
|
* ```
|
|
7750
8595
|
*/
|
|
7751
|
-
this.dump = async (symbol, strategyName, path = "./dump/schedule") => {
|
|
8596
|
+
this.dump = async (symbol, strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) => {
|
|
7752
8597
|
this.loggerService.log("scheduleMarkdownService dump", {
|
|
7753
8598
|
symbol,
|
|
7754
8599
|
strategyName,
|
|
7755
8600
|
path,
|
|
7756
8601
|
});
|
|
7757
8602
|
const storage = this.getStorage(symbol, strategyName);
|
|
7758
|
-
await storage.dump(strategyName, path);
|
|
8603
|
+
await storage.dump(strategyName, path, columns);
|
|
7759
8604
|
};
|
|
7760
8605
|
/**
|
|
7761
8606
|
* Clears accumulated event data from storage.
|
|
@@ -7814,86 +8659,6 @@ function percentile(sortedArray, p) {
|
|
|
7814
8659
|
const index = Math.ceil((sortedArray.length * p) / 100) - 1;
|
|
7815
8660
|
return sortedArray[Math.max(0, index)];
|
|
7816
8661
|
}
|
|
7817
|
-
const columns$3 = [
|
|
7818
|
-
{
|
|
7819
|
-
key: "metricType",
|
|
7820
|
-
label: "Metric Type",
|
|
7821
|
-
format: (data) => data.metricType,
|
|
7822
|
-
isVisible: () => true,
|
|
7823
|
-
},
|
|
7824
|
-
{
|
|
7825
|
-
key: "count",
|
|
7826
|
-
label: "Count",
|
|
7827
|
-
format: (data) => data.count.toString(),
|
|
7828
|
-
isVisible: () => true,
|
|
7829
|
-
},
|
|
7830
|
-
{
|
|
7831
|
-
key: "totalDuration",
|
|
7832
|
-
label: "Total (ms)",
|
|
7833
|
-
format: (data) => data.totalDuration.toFixed(2),
|
|
7834
|
-
isVisible: () => true,
|
|
7835
|
-
},
|
|
7836
|
-
{
|
|
7837
|
-
key: "avgDuration",
|
|
7838
|
-
label: "Avg (ms)",
|
|
7839
|
-
format: (data) => data.avgDuration.toFixed(2),
|
|
7840
|
-
isVisible: () => true,
|
|
7841
|
-
},
|
|
7842
|
-
{
|
|
7843
|
-
key: "minDuration",
|
|
7844
|
-
label: "Min (ms)",
|
|
7845
|
-
format: (data) => data.minDuration.toFixed(2),
|
|
7846
|
-
isVisible: () => true,
|
|
7847
|
-
},
|
|
7848
|
-
{
|
|
7849
|
-
key: "maxDuration",
|
|
7850
|
-
label: "Max (ms)",
|
|
7851
|
-
format: (data) => data.maxDuration.toFixed(2),
|
|
7852
|
-
isVisible: () => true,
|
|
7853
|
-
},
|
|
7854
|
-
{
|
|
7855
|
-
key: "stdDev",
|
|
7856
|
-
label: "Std Dev (ms)",
|
|
7857
|
-
format: (data) => data.stdDev.toFixed(2),
|
|
7858
|
-
isVisible: () => true,
|
|
7859
|
-
},
|
|
7860
|
-
{
|
|
7861
|
-
key: "median",
|
|
7862
|
-
label: "Median (ms)",
|
|
7863
|
-
format: (data) => data.median.toFixed(2),
|
|
7864
|
-
isVisible: () => true,
|
|
7865
|
-
},
|
|
7866
|
-
{
|
|
7867
|
-
key: "p95",
|
|
7868
|
-
label: "P95 (ms)",
|
|
7869
|
-
format: (data) => data.p95.toFixed(2),
|
|
7870
|
-
isVisible: () => true,
|
|
7871
|
-
},
|
|
7872
|
-
{
|
|
7873
|
-
key: "p99",
|
|
7874
|
-
label: "P99 (ms)",
|
|
7875
|
-
format: (data) => data.p99.toFixed(2),
|
|
7876
|
-
isVisible: () => true,
|
|
7877
|
-
},
|
|
7878
|
-
{
|
|
7879
|
-
key: "avgWaitTime",
|
|
7880
|
-
label: "Avg Wait (ms)",
|
|
7881
|
-
format: (data) => data.avgWaitTime.toFixed(2),
|
|
7882
|
-
isVisible: () => true,
|
|
7883
|
-
},
|
|
7884
|
-
{
|
|
7885
|
-
key: "minWaitTime",
|
|
7886
|
-
label: "Min Wait (ms)",
|
|
7887
|
-
format: (data) => data.minWaitTime.toFixed(2),
|
|
7888
|
-
isVisible: () => true,
|
|
7889
|
-
},
|
|
7890
|
-
{
|
|
7891
|
-
key: "maxWaitTime",
|
|
7892
|
-
label: "Max Wait (ms)",
|
|
7893
|
-
format: (data) => data.maxWaitTime.toFixed(2),
|
|
7894
|
-
isVisible: () => true,
|
|
7895
|
-
},
|
|
7896
|
-
];
|
|
7897
8662
|
/** Maximum number of performance events to store per strategy */
|
|
7898
8663
|
const MAX_EVENTS$3 = 10000;
|
|
7899
8664
|
/**
|
|
@@ -7996,9 +8761,10 @@ class PerformanceStorage {
|
|
|
7996
8761
|
* Generates markdown report with performance statistics.
|
|
7997
8762
|
*
|
|
7998
8763
|
* @param strategyName - Strategy name
|
|
8764
|
+
* @param columns - Column configuration for formatting the table
|
|
7999
8765
|
* @returns Markdown formatted report
|
|
8000
8766
|
*/
|
|
8001
|
-
async getReport(strategyName) {
|
|
8767
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.performance_columns) {
|
|
8002
8768
|
const stats = await this.getData(strategyName);
|
|
8003
8769
|
if (stats.totalEvents === 0) {
|
|
8004
8770
|
return [
|
|
@@ -8010,10 +8776,15 @@ class PerformanceStorage {
|
|
|
8010
8776
|
// Sort metrics by total duration (descending) to show bottlenecks first
|
|
8011
8777
|
const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
|
|
8012
8778
|
// Generate summary table using Column interface
|
|
8013
|
-
const visibleColumns =
|
|
8779
|
+
const visibleColumns = [];
|
|
8780
|
+
for (const col of columns) {
|
|
8781
|
+
if (await col.isVisible()) {
|
|
8782
|
+
visibleColumns.push(col);
|
|
8783
|
+
}
|
|
8784
|
+
}
|
|
8014
8785
|
const header = visibleColumns.map((col) => col.label);
|
|
8015
8786
|
const separator = visibleColumns.map(() => "---");
|
|
8016
|
-
const rows = sortedMetrics.map((metric) => visibleColumns.map((col) => col.format(metric)));
|
|
8787
|
+
const rows = await Promise.all(sortedMetrics.map(async (metric, index) => Promise.all(visibleColumns.map((col) => col.format(metric, index)))));
|
|
8017
8788
|
const tableData = [header, separator, ...rows];
|
|
8018
8789
|
const summaryTable = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8019
8790
|
// Calculate percentage of total time for each metric
|
|
@@ -8044,9 +8815,10 @@ class PerformanceStorage {
|
|
|
8044
8815
|
*
|
|
8045
8816
|
* @param strategyName - Strategy name
|
|
8046
8817
|
* @param path - Directory path to save report
|
|
8818
|
+
* @param columns - Column configuration for formatting the table
|
|
8047
8819
|
*/
|
|
8048
|
-
async dump(strategyName, path = "./dump/performance") {
|
|
8049
|
-
const markdown = await this.getReport(strategyName);
|
|
8820
|
+
async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
8821
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
8050
8822
|
try {
|
|
8051
8823
|
const dir = join(process.cwd(), path);
|
|
8052
8824
|
await mkdir(dir, { recursive: true });
|
|
@@ -8139,6 +8911,7 @@ class PerformanceMarkdownService {
|
|
|
8139
8911
|
*
|
|
8140
8912
|
* @param symbol - Trading pair symbol
|
|
8141
8913
|
* @param strategyName - Strategy name to generate report for
|
|
8914
|
+
* @param columns - Column configuration for formatting the table
|
|
8142
8915
|
* @returns Markdown formatted report string
|
|
8143
8916
|
*
|
|
8144
8917
|
* @example
|
|
@@ -8147,13 +8920,13 @@ class PerformanceMarkdownService {
|
|
|
8147
8920
|
* console.log(markdown);
|
|
8148
8921
|
* ```
|
|
8149
8922
|
*/
|
|
8150
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8923
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.performance_columns) => {
|
|
8151
8924
|
this.loggerService.log("performanceMarkdownService getReport", {
|
|
8152
8925
|
symbol,
|
|
8153
8926
|
strategyName,
|
|
8154
8927
|
});
|
|
8155
8928
|
const storage = this.getStorage(symbol, strategyName);
|
|
8156
|
-
return storage.getReport(strategyName);
|
|
8929
|
+
return storage.getReport(strategyName, columns);
|
|
8157
8930
|
};
|
|
8158
8931
|
/**
|
|
8159
8932
|
* Saves performance report to disk.
|
|
@@ -8161,6 +8934,7 @@ class PerformanceMarkdownService {
|
|
|
8161
8934
|
* @param symbol - Trading pair symbol
|
|
8162
8935
|
* @param strategyName - Strategy name to save report for
|
|
8163
8936
|
* @param path - Directory path to save report
|
|
8937
|
+
* @param columns - Column configuration for formatting the table
|
|
8164
8938
|
*
|
|
8165
8939
|
* @example
|
|
8166
8940
|
* ```typescript
|
|
@@ -8171,14 +8945,14 @@ class PerformanceMarkdownService {
|
|
|
8171
8945
|
* await performanceService.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
8172
8946
|
* ```
|
|
8173
8947
|
*/
|
|
8174
|
-
this.dump = async (symbol, strategyName, path = "./dump/performance") => {
|
|
8948
|
+
this.dump = async (symbol, strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) => {
|
|
8175
8949
|
this.loggerService.log("performanceMarkdownService dump", {
|
|
8176
8950
|
symbol,
|
|
8177
8951
|
strategyName,
|
|
8178
8952
|
path,
|
|
8179
8953
|
});
|
|
8180
8954
|
const storage = this.getStorage(symbol, strategyName);
|
|
8181
|
-
await storage.dump(strategyName, path);
|
|
8955
|
+
await storage.dump(strategyName, path, columns);
|
|
8182
8956
|
};
|
|
8183
8957
|
/**
|
|
8184
8958
|
* Clears accumulated performance data from storage.
|
|
@@ -8237,135 +9011,6 @@ function formatMetric(value) {
|
|
|
8237
9011
|
}
|
|
8238
9012
|
return value.toFixed(2);
|
|
8239
9013
|
}
|
|
8240
|
-
/**
|
|
8241
|
-
* Creates strategy comparison columns based on metric name.
|
|
8242
|
-
* Dynamically builds column configuration with metric-specific header.
|
|
8243
|
-
*
|
|
8244
|
-
* @param metric - Metric being optimized
|
|
8245
|
-
* @returns Array of column configurations for strategy comparison table
|
|
8246
|
-
*/
|
|
8247
|
-
function createStrategyColumns(metric) {
|
|
8248
|
-
return [
|
|
8249
|
-
{
|
|
8250
|
-
key: "rank",
|
|
8251
|
-
label: "Rank",
|
|
8252
|
-
format: (data, index) => `${index + 1}`,
|
|
8253
|
-
isVisible: () => true,
|
|
8254
|
-
},
|
|
8255
|
-
{
|
|
8256
|
-
key: "strategy",
|
|
8257
|
-
label: "Strategy",
|
|
8258
|
-
format: (data) => data.strategyName,
|
|
8259
|
-
isVisible: () => true,
|
|
8260
|
-
},
|
|
8261
|
-
{
|
|
8262
|
-
key: "metric",
|
|
8263
|
-
label: metric,
|
|
8264
|
-
format: (data) => formatMetric(data.metricValue),
|
|
8265
|
-
isVisible: () => true,
|
|
8266
|
-
},
|
|
8267
|
-
{
|
|
8268
|
-
key: "totalSignals",
|
|
8269
|
-
label: "Total Signals",
|
|
8270
|
-
format: (data) => `${data.stats.totalSignals}`,
|
|
8271
|
-
isVisible: () => true,
|
|
8272
|
-
},
|
|
8273
|
-
{
|
|
8274
|
-
key: "winRate",
|
|
8275
|
-
label: "Win Rate",
|
|
8276
|
-
format: (data) => data.stats.winRate !== null
|
|
8277
|
-
? `${data.stats.winRate.toFixed(2)}%`
|
|
8278
|
-
: "N/A",
|
|
8279
|
-
isVisible: () => true,
|
|
8280
|
-
},
|
|
8281
|
-
{
|
|
8282
|
-
key: "avgPnl",
|
|
8283
|
-
label: "Avg PNL",
|
|
8284
|
-
format: (data) => data.stats.avgPnl !== null
|
|
8285
|
-
? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
|
|
8286
|
-
: "N/A",
|
|
8287
|
-
isVisible: () => true,
|
|
8288
|
-
},
|
|
8289
|
-
{
|
|
8290
|
-
key: "totalPnl",
|
|
8291
|
-
label: "Total PNL",
|
|
8292
|
-
format: (data) => data.stats.totalPnl !== null
|
|
8293
|
-
? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
|
|
8294
|
-
: "N/A",
|
|
8295
|
-
isVisible: () => true,
|
|
8296
|
-
},
|
|
8297
|
-
{
|
|
8298
|
-
key: "sharpeRatio",
|
|
8299
|
-
label: "Sharpe Ratio",
|
|
8300
|
-
format: (data) => data.stats.sharpeRatio !== null
|
|
8301
|
-
? `${data.stats.sharpeRatio.toFixed(3)}`
|
|
8302
|
-
: "N/A",
|
|
8303
|
-
isVisible: () => true,
|
|
8304
|
-
},
|
|
8305
|
-
{
|
|
8306
|
-
key: "stdDev",
|
|
8307
|
-
label: "Std Dev",
|
|
8308
|
-
format: (data) => data.stats.stdDev !== null
|
|
8309
|
-
? `${data.stats.stdDev.toFixed(3)}%`
|
|
8310
|
-
: "N/A",
|
|
8311
|
-
isVisible: () => true,
|
|
8312
|
-
},
|
|
8313
|
-
];
|
|
8314
|
-
}
|
|
8315
|
-
/**
|
|
8316
|
-
* Column configuration for PNL table.
|
|
8317
|
-
* Defines all columns for displaying closed signals across strategies.
|
|
8318
|
-
*/
|
|
8319
|
-
const pnlColumns = [
|
|
8320
|
-
{
|
|
8321
|
-
key: "strategy",
|
|
8322
|
-
label: "Strategy",
|
|
8323
|
-
format: (data) => data.strategyName,
|
|
8324
|
-
isVisible: () => true,
|
|
8325
|
-
},
|
|
8326
|
-
{
|
|
8327
|
-
key: "signalId",
|
|
8328
|
-
label: "Signal ID",
|
|
8329
|
-
format: (data) => data.signalId,
|
|
8330
|
-
isVisible: () => true,
|
|
8331
|
-
},
|
|
8332
|
-
{
|
|
8333
|
-
key: "symbol",
|
|
8334
|
-
label: "Symbol",
|
|
8335
|
-
format: (data) => data.symbol,
|
|
8336
|
-
isVisible: () => true,
|
|
8337
|
-
},
|
|
8338
|
-
{
|
|
8339
|
-
key: "position",
|
|
8340
|
-
label: "Position",
|
|
8341
|
-
format: (data) => data.position.toUpperCase(),
|
|
8342
|
-
isVisible: () => true,
|
|
8343
|
-
},
|
|
8344
|
-
{
|
|
8345
|
-
key: "pnl",
|
|
8346
|
-
label: "PNL (net)",
|
|
8347
|
-
format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
|
|
8348
|
-
isVisible: () => true,
|
|
8349
|
-
},
|
|
8350
|
-
{
|
|
8351
|
-
key: "closeReason",
|
|
8352
|
-
label: "Close Reason",
|
|
8353
|
-
format: (data) => data.closeReason,
|
|
8354
|
-
isVisible: () => true,
|
|
8355
|
-
},
|
|
8356
|
-
{
|
|
8357
|
-
key: "openTime",
|
|
8358
|
-
label: "Open Time",
|
|
8359
|
-
format: (data) => new Date(data.openTime).toISOString(),
|
|
8360
|
-
isVisible: () => true,
|
|
8361
|
-
},
|
|
8362
|
-
{
|
|
8363
|
-
key: "closeTime",
|
|
8364
|
-
label: "Close Time",
|
|
8365
|
-
format: (data) => new Date(data.closeTime).toISOString(),
|
|
8366
|
-
isVisible: () => true,
|
|
8367
|
-
},
|
|
8368
|
-
];
|
|
8369
9014
|
/**
|
|
8370
9015
|
* Storage class for accumulating walker results.
|
|
8371
9016
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
@@ -8397,7 +9042,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8397
9042
|
this._strategyResults.unshift({
|
|
8398
9043
|
strategyName: data.strategyName,
|
|
8399
9044
|
stats: data.stats,
|
|
8400
|
-
metricValue: data.metricValue,
|
|
9045
|
+
metricValue: isUnsafe$1(data.metricValue) ? null : data.metricValue,
|
|
8401
9046
|
});
|
|
8402
9047
|
}
|
|
8403
9048
|
/**
|
|
@@ -8430,11 +9075,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8430
9075
|
* Generates comparison table for top N strategies (View).
|
|
8431
9076
|
* Sorts strategies by metric value and formats as markdown table.
|
|
8432
9077
|
*
|
|
8433
|
-
* @param metric - Metric being optimized
|
|
8434
9078
|
* @param topN - Number of top strategies to include (default: 10)
|
|
9079
|
+
* @param columns - Column configuration for formatting the strategy comparison table
|
|
8435
9080
|
* @returns Markdown formatted comparison table
|
|
8436
9081
|
*/
|
|
8437
|
-
getComparisonTable(
|
|
9082
|
+
async getComparisonTable(topN = 10, columns = COLUMN_CONFIG.walker_strategy_columns) {
|
|
8438
9083
|
if (this._strategyResults.length === 0) {
|
|
8439
9084
|
return "No strategy results available.";
|
|
8440
9085
|
}
|
|
@@ -8447,13 +9092,17 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8447
9092
|
// Take top N strategies
|
|
8448
9093
|
const topStrategies = sortedResults.slice(0, topN);
|
|
8449
9094
|
// Get columns configuration
|
|
8450
|
-
const
|
|
8451
|
-
const
|
|
9095
|
+
const visibleColumns = [];
|
|
9096
|
+
for (const col of columns) {
|
|
9097
|
+
if (await col.isVisible()) {
|
|
9098
|
+
visibleColumns.push(col);
|
|
9099
|
+
}
|
|
9100
|
+
}
|
|
8452
9101
|
// Build table header
|
|
8453
9102
|
const header = visibleColumns.map((col) => col.label);
|
|
8454
9103
|
const separator = visibleColumns.map(() => "---");
|
|
8455
9104
|
// Build table rows
|
|
8456
|
-
const rows = topStrategies.map((result, index) => visibleColumns.map((col) => col.format(result, index)));
|
|
9105
|
+
const rows = await Promise.all(topStrategies.map(async (result, index) => Promise.all(visibleColumns.map((col) => col.format(result, index)))));
|
|
8457
9106
|
const tableData = [header, separator, ...rows];
|
|
8458
9107
|
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8459
9108
|
}
|
|
@@ -8461,9 +9110,10 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8461
9110
|
* Generates PNL table showing all closed signals across all strategies (View).
|
|
8462
9111
|
* Collects all signals from all strategies and formats as markdown table.
|
|
8463
9112
|
*
|
|
9113
|
+
* @param columns - Column configuration for formatting the PNL table
|
|
8464
9114
|
* @returns Markdown formatted PNL table
|
|
8465
9115
|
*/
|
|
8466
|
-
getPnlTable() {
|
|
9116
|
+
async getPnlTable(columns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
8467
9117
|
if (this._strategyResults.length === 0) {
|
|
8468
9118
|
return "No strategy results available.";
|
|
8469
9119
|
}
|
|
@@ -8487,11 +9137,16 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8487
9137
|
return "No closed signals available.";
|
|
8488
9138
|
}
|
|
8489
9139
|
// Build table header
|
|
8490
|
-
const visibleColumns =
|
|
9140
|
+
const visibleColumns = [];
|
|
9141
|
+
for (const col of columns) {
|
|
9142
|
+
if (await col.isVisible()) {
|
|
9143
|
+
visibleColumns.push(col);
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
8491
9146
|
const header = visibleColumns.map((col) => col.label);
|
|
8492
9147
|
const separator = visibleColumns.map(() => "---");
|
|
8493
9148
|
// Build table rows
|
|
8494
|
-
const rows = allSignals.map((signal) => visibleColumns.map((col) => col.format(signal)));
|
|
9149
|
+
const rows = await Promise.all(allSignals.map(async (signal, index) => Promise.all(visibleColumns.map((col) => col.format(signal, index)))));
|
|
8495
9150
|
const tableData = [header, separator, ...rows];
|
|
8496
9151
|
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8497
9152
|
}
|
|
@@ -8502,9 +9157,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8502
9157
|
* @param symbol - Trading symbol
|
|
8503
9158
|
* @param metric - Metric being optimized
|
|
8504
9159
|
* @param context - Context with exchangeName and frameName
|
|
9160
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9161
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8505
9162
|
* @returns Markdown formatted report with all results
|
|
8506
9163
|
*/
|
|
8507
|
-
async getReport(symbol, metric, context) {
|
|
9164
|
+
async getReport(symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
8508
9165
|
const results = await this.getData(symbol, metric, context);
|
|
8509
9166
|
// Get total signals for best strategy
|
|
8510
9167
|
const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
|
|
@@ -8524,11 +9181,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8524
9181
|
"",
|
|
8525
9182
|
"## Top Strategies Comparison",
|
|
8526
9183
|
"",
|
|
8527
|
-
this.getComparisonTable(
|
|
9184
|
+
await this.getComparisonTable(10, strategyColumns),
|
|
8528
9185
|
"",
|
|
8529
9186
|
"## All Signals (PNL Table)",
|
|
8530
9187
|
"",
|
|
8531
|
-
this.getPnlTable(),
|
|
9188
|
+
await this.getPnlTable(pnlColumns),
|
|
8532
9189
|
"",
|
|
8533
9190
|
"**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
|
|
8534
9191
|
].join("\n");
|
|
@@ -8540,9 +9197,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8540
9197
|
* @param metric - Metric being optimized
|
|
8541
9198
|
* @param context - Context with exchangeName and frameName
|
|
8542
9199
|
* @param path - Directory path to save report (default: "./dump/walker")
|
|
9200
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9201
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8543
9202
|
*/
|
|
8544
|
-
async dump(symbol, metric, context, path = "./dump/walker") {
|
|
8545
|
-
const markdown = await this.getReport(symbol, metric, context);
|
|
9203
|
+
async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
9204
|
+
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
8546
9205
|
try {
|
|
8547
9206
|
const dir = join(process.cwd(), path);
|
|
8548
9207
|
await mkdir(dir, { recursive: true });
|
|
@@ -8635,6 +9294,8 @@ class WalkerMarkdownService {
|
|
|
8635
9294
|
* @param symbol - Trading symbol
|
|
8636
9295
|
* @param metric - Metric being optimized
|
|
8637
9296
|
* @param context - Context with exchangeName and frameName
|
|
9297
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9298
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8638
9299
|
* @returns Markdown formatted report string
|
|
8639
9300
|
*
|
|
8640
9301
|
* @example
|
|
@@ -8644,7 +9305,7 @@ class WalkerMarkdownService {
|
|
|
8644
9305
|
* console.log(markdown);
|
|
8645
9306
|
* ```
|
|
8646
9307
|
*/
|
|
8647
|
-
this.getReport = async (walkerName, symbol, metric, context) => {
|
|
9308
|
+
this.getReport = async (walkerName, symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
|
|
8648
9309
|
this.loggerService.log("walkerMarkdownService getReport", {
|
|
8649
9310
|
walkerName,
|
|
8650
9311
|
symbol,
|
|
@@ -8652,7 +9313,7 @@ class WalkerMarkdownService {
|
|
|
8652
9313
|
context,
|
|
8653
9314
|
});
|
|
8654
9315
|
const storage = this.getStorage(walkerName);
|
|
8655
|
-
return storage.getReport(symbol, metric, context);
|
|
9316
|
+
return storage.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
8656
9317
|
};
|
|
8657
9318
|
/**
|
|
8658
9319
|
* Saves walker report to disk.
|
|
@@ -8664,6 +9325,8 @@ class WalkerMarkdownService {
|
|
|
8664
9325
|
* @param metric - Metric being optimized
|
|
8665
9326
|
* @param context - Context with exchangeName and frameName
|
|
8666
9327
|
* @param path - Directory path to save report (default: "./dump/walker")
|
|
9328
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9329
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8667
9330
|
*
|
|
8668
9331
|
* @example
|
|
8669
9332
|
* ```typescript
|
|
@@ -8676,7 +9339,7 @@ class WalkerMarkdownService {
|
|
|
8676
9339
|
* await service.dump("my-walker", "BTCUSDT", "sharpeRatio", { exchangeName: "binance", frameName: "1d" }, "./custom/path");
|
|
8677
9340
|
* ```
|
|
8678
9341
|
*/
|
|
8679
|
-
this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker") => {
|
|
9342
|
+
this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
|
|
8680
9343
|
this.loggerService.log("walkerMarkdownService dump", {
|
|
8681
9344
|
walkerName,
|
|
8682
9345
|
symbol,
|
|
@@ -8685,7 +9348,7 @@ class WalkerMarkdownService {
|
|
|
8685
9348
|
path,
|
|
8686
9349
|
});
|
|
8687
9350
|
const storage = this.getStorage(walkerName);
|
|
8688
|
-
await storage.dump(symbol, metric, context, path);
|
|
9351
|
+
await storage.dump(symbol, metric, context, path, strategyColumns, pnlColumns);
|
|
8689
9352
|
};
|
|
8690
9353
|
/**
|
|
8691
9354
|
* Clears accumulated result data from storage.
|
|
@@ -8751,80 +9414,6 @@ function isUnsafe(value) {
|
|
|
8751
9414
|
}
|
|
8752
9415
|
return false;
|
|
8753
9416
|
}
|
|
8754
|
-
const columns$2 = [
|
|
8755
|
-
{
|
|
8756
|
-
key: "symbol",
|
|
8757
|
-
label: "Symbol",
|
|
8758
|
-
format: (data) => data.symbol,
|
|
8759
|
-
isVisible: () => true,
|
|
8760
|
-
},
|
|
8761
|
-
{
|
|
8762
|
-
key: "totalPnl",
|
|
8763
|
-
label: "Total PNL",
|
|
8764
|
-
format: (data) => data.totalPnl !== null ? str(data.totalPnl, "%+.2f%%") : "N/A",
|
|
8765
|
-
isVisible: () => true,
|
|
8766
|
-
},
|
|
8767
|
-
{
|
|
8768
|
-
key: "sharpeRatio",
|
|
8769
|
-
label: "Sharpe",
|
|
8770
|
-
format: (data) => data.sharpeRatio !== null ? str(data.sharpeRatio, "%.2f") : "N/A",
|
|
8771
|
-
isVisible: () => true,
|
|
8772
|
-
},
|
|
8773
|
-
{
|
|
8774
|
-
key: "profitFactor",
|
|
8775
|
-
label: "PF",
|
|
8776
|
-
format: (data) => data.profitFactor !== null ? str(data.profitFactor, "%.2f") : "N/A",
|
|
8777
|
-
isVisible: () => true,
|
|
8778
|
-
},
|
|
8779
|
-
{
|
|
8780
|
-
key: "expectancy",
|
|
8781
|
-
label: "Expect",
|
|
8782
|
-
format: (data) => data.expectancy !== null ? str(data.expectancy, "%+.2f%%") : "N/A",
|
|
8783
|
-
isVisible: () => true,
|
|
8784
|
-
},
|
|
8785
|
-
{
|
|
8786
|
-
key: "winRate",
|
|
8787
|
-
label: "WR",
|
|
8788
|
-
format: (data) => data.winRate !== null ? str(data.winRate, "%.1f%%") : "N/A",
|
|
8789
|
-
isVisible: () => true,
|
|
8790
|
-
},
|
|
8791
|
-
{
|
|
8792
|
-
key: "avgWin",
|
|
8793
|
-
label: "Avg Win",
|
|
8794
|
-
format: (data) => data.avgWin !== null ? str(data.avgWin, "%+.2f%%") : "N/A",
|
|
8795
|
-
isVisible: () => true,
|
|
8796
|
-
},
|
|
8797
|
-
{
|
|
8798
|
-
key: "avgLoss",
|
|
8799
|
-
label: "Avg Loss",
|
|
8800
|
-
format: (data) => data.avgLoss !== null ? str(data.avgLoss, "%+.2f%%") : "N/A",
|
|
8801
|
-
isVisible: () => true,
|
|
8802
|
-
},
|
|
8803
|
-
{
|
|
8804
|
-
key: "maxDrawdown",
|
|
8805
|
-
label: "Max DD",
|
|
8806
|
-
format: (data) => data.maxDrawdown !== null ? str(-data.maxDrawdown, "%.2f%%") : "N/A",
|
|
8807
|
-
isVisible: () => true,
|
|
8808
|
-
},
|
|
8809
|
-
{
|
|
8810
|
-
key: "maxWinStreak",
|
|
8811
|
-
label: "W Streak",
|
|
8812
|
-
format: (data) => data.maxWinStreak.toString(),
|
|
8813
|
-
isVisible: () => true,
|
|
8814
|
-
},
|
|
8815
|
-
{
|
|
8816
|
-
key: "maxLossStreak",
|
|
8817
|
-
label: "L Streak",
|
|
8818
|
-
format: (data) => data.maxLossStreak.toString(),
|
|
8819
|
-
isVisible: () => true,
|
|
8820
|
-
},
|
|
8821
|
-
{
|
|
8822
|
-
key: "totalTrades",
|
|
8823
|
-
label: "Trades",
|
|
8824
|
-
format: (data) => data.totalTrades.toString(),
|
|
8825
|
-
isVisible: () => true,
|
|
8826
|
-
},
|
|
8827
|
-
];
|
|
8828
9417
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
8829
9418
|
const MAX_EVENTS$2 = 250;
|
|
8830
9419
|
/**
|
|
@@ -9059,9 +9648,10 @@ class HeatmapStorage {
|
|
|
9059
9648
|
* Generates markdown report with portfolio heatmap table (View).
|
|
9060
9649
|
*
|
|
9061
9650
|
* @param strategyName - Strategy name for report title
|
|
9651
|
+
* @param columns - Column configuration for formatting the table
|
|
9062
9652
|
* @returns Promise resolving to markdown formatted report string
|
|
9063
9653
|
*/
|
|
9064
|
-
async getReport(strategyName) {
|
|
9654
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.heat_columns) {
|
|
9065
9655
|
const data = await this.getData();
|
|
9066
9656
|
if (data.symbols.length === 0) {
|
|
9067
9657
|
return [
|
|
@@ -9070,10 +9660,15 @@ class HeatmapStorage {
|
|
|
9070
9660
|
"*No data available*"
|
|
9071
9661
|
].join("\n");
|
|
9072
9662
|
}
|
|
9073
|
-
const visibleColumns =
|
|
9663
|
+
const visibleColumns = [];
|
|
9664
|
+
for (const col of columns) {
|
|
9665
|
+
if (await col.isVisible()) {
|
|
9666
|
+
visibleColumns.push(col);
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9074
9669
|
const header = visibleColumns.map((col) => col.label);
|
|
9075
9670
|
const separator = visibleColumns.map(() => "---");
|
|
9076
|
-
const rows = data.symbols.map((row) => visibleColumns.map((col) => col.format(row)));
|
|
9671
|
+
const rows = await Promise.all(data.symbols.map(async (row, index) => Promise.all(visibleColumns.map((col) => col.format(row, index)))));
|
|
9077
9672
|
const tableData = [header, separator, ...rows];
|
|
9078
9673
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
9079
9674
|
return [
|
|
@@ -9089,9 +9684,10 @@ class HeatmapStorage {
|
|
|
9089
9684
|
*
|
|
9090
9685
|
* @param strategyName - Strategy name for filename
|
|
9091
9686
|
* @param path - Directory path to save report (default: "./dump/heatmap")
|
|
9687
|
+
* @param columns - Column configuration for formatting the table
|
|
9092
9688
|
*/
|
|
9093
|
-
async dump(strategyName, path = "./dump/heatmap") {
|
|
9094
|
-
const markdown = await this.getReport(strategyName);
|
|
9689
|
+
async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
9690
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
9095
9691
|
try {
|
|
9096
9692
|
const dir = join(process.cwd(), path);
|
|
9097
9693
|
await mkdir(dir, { recursive: true });
|
|
@@ -9188,6 +9784,7 @@ class HeatMarkdownService {
|
|
|
9188
9784
|
* Generates markdown report with portfolio heatmap table for a strategy.
|
|
9189
9785
|
*
|
|
9190
9786
|
* @param strategyName - Strategy name to generate heatmap report for
|
|
9787
|
+
* @param columns - Column configuration for formatting the table
|
|
9191
9788
|
* @returns Promise resolving to markdown formatted report string
|
|
9192
9789
|
*
|
|
9193
9790
|
* @example
|
|
@@ -9207,12 +9804,12 @@ class HeatMarkdownService {
|
|
|
9207
9804
|
* // ...
|
|
9208
9805
|
* ```
|
|
9209
9806
|
*/
|
|
9210
|
-
this.getReport = async (strategyName) => {
|
|
9807
|
+
this.getReport = async (strategyName, columns = COLUMN_CONFIG.heat_columns) => {
|
|
9211
9808
|
this.loggerService.log(HEATMAP_METHOD_NAME_GET_REPORT, {
|
|
9212
9809
|
strategyName,
|
|
9213
9810
|
});
|
|
9214
9811
|
const storage = this.getStorage(strategyName);
|
|
9215
|
-
return storage.getReport(strategyName);
|
|
9812
|
+
return storage.getReport(strategyName, columns);
|
|
9216
9813
|
};
|
|
9217
9814
|
/**
|
|
9218
9815
|
* Saves heatmap report to disk for a strategy.
|
|
@@ -9222,6 +9819,7 @@ class HeatMarkdownService {
|
|
|
9222
9819
|
*
|
|
9223
9820
|
* @param strategyName - Strategy name to save heatmap report for
|
|
9224
9821
|
* @param path - Optional directory path to save report (default: "./dump/heatmap")
|
|
9822
|
+
* @param columns - Column configuration for formatting the table
|
|
9225
9823
|
*
|
|
9226
9824
|
* @example
|
|
9227
9825
|
* ```typescript
|
|
@@ -9234,13 +9832,13 @@ class HeatMarkdownService {
|
|
|
9234
9832
|
* await service.dump("my-strategy", "./reports");
|
|
9235
9833
|
* ```
|
|
9236
9834
|
*/
|
|
9237
|
-
this.dump = async (strategyName, path = "./dump/heatmap") => {
|
|
9835
|
+
this.dump = async (strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) => {
|
|
9238
9836
|
this.loggerService.log(HEATMAP_METHOD_NAME_DUMP, {
|
|
9239
9837
|
strategyName,
|
|
9240
9838
|
path,
|
|
9241
9839
|
});
|
|
9242
9840
|
const storage = this.getStorage(strategyName);
|
|
9243
|
-
await storage.dump(strategyName, path);
|
|
9841
|
+
await storage.dump(strategyName, path, columns);
|
|
9244
9842
|
};
|
|
9245
9843
|
/**
|
|
9246
9844
|
* Clears accumulated heatmap data from storage.
|
|
@@ -11094,6 +11692,9 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
|
|
|
11094
11692
|
if (self._states === NEED_FETCH) {
|
|
11095
11693
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11096
11694
|
}
|
|
11695
|
+
if (data.id !== self.params.signalId) {
|
|
11696
|
+
throw new Error(`Signal ID mismatch: expected ${self.params.signalId}, got ${data.id}`);
|
|
11697
|
+
}
|
|
11097
11698
|
let state = self._states.get(data.id);
|
|
11098
11699
|
if (!state) {
|
|
11099
11700
|
state = {
|
|
@@ -11118,7 +11719,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
|
|
|
11118
11719
|
}
|
|
11119
11720
|
}
|
|
11120
11721
|
if (shouldPersist) {
|
|
11121
|
-
await self._persistState(symbol,
|
|
11722
|
+
await self._persistState(symbol, data.strategyName);
|
|
11122
11723
|
}
|
|
11123
11724
|
};
|
|
11124
11725
|
/**
|
|
@@ -11140,6 +11741,9 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
11140
11741
|
if (self._states === NEED_FETCH) {
|
|
11141
11742
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11142
11743
|
}
|
|
11744
|
+
if (data.id !== self.params.signalId) {
|
|
11745
|
+
throw new Error(`Signal ID mismatch: expected ${self.params.signalId}, got ${data.id}`);
|
|
11746
|
+
}
|
|
11143
11747
|
let state = self._states.get(data.id);
|
|
11144
11748
|
if (!state) {
|
|
11145
11749
|
state = {
|
|
@@ -11165,7 +11769,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
11165
11769
|
}
|
|
11166
11770
|
}
|
|
11167
11771
|
if (shouldPersist) {
|
|
11168
|
-
await self._persistState(symbol,
|
|
11772
|
+
await self._persistState(symbol, data.strategyName);
|
|
11169
11773
|
}
|
|
11170
11774
|
};
|
|
11171
11775
|
/**
|
|
@@ -11174,15 +11778,29 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
11174
11778
|
* Loads persisted partial state from disk and restores in-memory Maps.
|
|
11175
11779
|
* Converts serialized arrays back to Sets for O(1) lookups.
|
|
11176
11780
|
*
|
|
11781
|
+
* ONLY runs in LIVE mode (backtest=false). In backtest mode, state is not persisted.
|
|
11782
|
+
*
|
|
11177
11783
|
* @param symbol - Trading pair symbol
|
|
11784
|
+
* @param strategyName - Strategy identifier
|
|
11785
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
11178
11786
|
* @param self - ClientPartial instance reference
|
|
11179
11787
|
*/
|
|
11180
|
-
const WAIT_FOR_INIT_FN = async (symbol, self) => {
|
|
11181
|
-
self.params.logger.debug("ClientPartial waitForInit", {
|
|
11182
|
-
|
|
11183
|
-
|
|
11788
|
+
const WAIT_FOR_INIT_FN = async (symbol, strategyName, self) => {
|
|
11789
|
+
self.params.logger.debug("ClientPartial waitForInit", {
|
|
11790
|
+
symbol,
|
|
11791
|
+
strategyName,
|
|
11792
|
+
backtest: self.params.backtest
|
|
11793
|
+
});
|
|
11794
|
+
if (self._states !== NEED_FETCH) {
|
|
11795
|
+
throw new Error("ClientPartial WAIT_FOR_INIT_FN should be called once!");
|
|
11796
|
+
}
|
|
11797
|
+
self._states = new Map();
|
|
11798
|
+
// Skip persistence in backtest mode
|
|
11799
|
+
if (self.params.backtest) {
|
|
11800
|
+
self.params.logger.debug("ClientPartial waitForInit: skipping persist read in backtest mode");
|
|
11801
|
+
return;
|
|
11184
11802
|
}
|
|
11185
|
-
const partialData = await PersistPartialAdapter.readPartialData(symbol);
|
|
11803
|
+
const partialData = await PersistPartialAdapter.readPartialData(symbol, strategyName);
|
|
11186
11804
|
for (const [signalId, data] of Object.entries(partialData)) {
|
|
11187
11805
|
const state = {
|
|
11188
11806
|
profitLevels: new Set(data.profitLevels),
|
|
@@ -11192,6 +11810,7 @@ const WAIT_FOR_INIT_FN = async (symbol, self) => {
|
|
|
11192
11810
|
}
|
|
11193
11811
|
self.params.logger.info("ClientPartial restored state", {
|
|
11194
11812
|
symbol,
|
|
11813
|
+
strategyName,
|
|
11195
11814
|
signalCount: Object.keys(partialData).length,
|
|
11196
11815
|
});
|
|
11197
11816
|
};
|
|
@@ -11270,23 +11889,24 @@ class ClientPartial {
|
|
|
11270
11889
|
/**
|
|
11271
11890
|
* Initializes partial state by loading from disk.
|
|
11272
11891
|
*
|
|
11273
|
-
* Uses singleshot pattern to ensure initialization happens exactly once per symbol.
|
|
11892
|
+
* Uses singleshot pattern to ensure initialization happens exactly once per symbol:strategyName.
|
|
11274
11893
|
* Reads persisted state from PersistPartialAdapter and restores to _states Map.
|
|
11275
11894
|
*
|
|
11276
11895
|
* Must be called before profit()/loss()/clear() methods.
|
|
11277
11896
|
*
|
|
11278
11897
|
* @param symbol - Trading pair symbol
|
|
11898
|
+
* @param strategyName - Strategy identifier
|
|
11899
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
11279
11900
|
* @returns Promise that resolves when initialization is complete
|
|
11280
11901
|
*
|
|
11281
11902
|
* @example
|
|
11282
11903
|
* ```typescript
|
|
11283
11904
|
* const partial = new ClientPartial(params);
|
|
11284
|
-
* await partial.waitForInit("BTCUSDT"); // Load persisted state
|
|
11905
|
+
* await partial.waitForInit("BTCUSDT", "my-strategy", false); // Load persisted state (live mode)
|
|
11285
11906
|
* // Now profit()/loss() can be called
|
|
11286
11907
|
* ```
|
|
11287
11908
|
*/
|
|
11288
|
-
this.waitForInit = singleshot(async (symbol) => await WAIT_FOR_INIT_FN(symbol, this));
|
|
11289
|
-
this._states = new Map();
|
|
11909
|
+
this.waitForInit = singleshot(async (symbol, strategyName) => await WAIT_FOR_INIT_FN(symbol, strategyName, this));
|
|
11290
11910
|
}
|
|
11291
11911
|
/**
|
|
11292
11912
|
* Persists current partial state to disk.
|
|
@@ -11299,13 +11919,15 @@ class ClientPartial {
|
|
|
11299
11919
|
* Uses atomic file writes via PersistPartialAdapter.
|
|
11300
11920
|
*
|
|
11301
11921
|
* @param symbol - Trading pair symbol
|
|
11922
|
+
* @param strategyName - Strategy identifier
|
|
11923
|
+
* @param backtest - True if backtest mode
|
|
11302
11924
|
* @returns Promise that resolves when persistence is complete
|
|
11303
11925
|
*/
|
|
11304
|
-
async _persistState(symbol,
|
|
11305
|
-
if (backtest) {
|
|
11926
|
+
async _persistState(symbol, strategyName) {
|
|
11927
|
+
if (this.params.backtest) {
|
|
11306
11928
|
return;
|
|
11307
11929
|
}
|
|
11308
|
-
this.params.logger.debug("ClientPartial persistState", { symbol });
|
|
11930
|
+
this.params.logger.debug("ClientPartial persistState", { symbol, strategyName });
|
|
11309
11931
|
if (this._states === NEED_FETCH) {
|
|
11310
11932
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11311
11933
|
}
|
|
@@ -11316,7 +11938,7 @@ class ClientPartial {
|
|
|
11316
11938
|
lossLevels: Array.from(state.lossLevels),
|
|
11317
11939
|
};
|
|
11318
11940
|
}
|
|
11319
|
-
await PersistPartialAdapter.writePartialData(partialData, symbol);
|
|
11941
|
+
await PersistPartialAdapter.writePartialData(partialData, symbol, strategyName);
|
|
11320
11942
|
}
|
|
11321
11943
|
/**
|
|
11322
11944
|
* Processes profit state and emits events for newly reached profit levels.
|
|
@@ -11437,8 +12059,11 @@ class ClientPartial {
|
|
|
11437
12059
|
if (this._states === NEED_FETCH) {
|
|
11438
12060
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11439
12061
|
}
|
|
12062
|
+
if (data.id !== this.params.signalId) {
|
|
12063
|
+
throw new Error(`Signal ID mismatch: expected ${this.params.signalId}, got ${data.id}`);
|
|
12064
|
+
}
|
|
11440
12065
|
this._states.delete(data.id);
|
|
11441
|
-
await this._persistState(symbol,
|
|
12066
|
+
await this._persistState(symbol, data.strategyName);
|
|
11442
12067
|
}
|
|
11443
12068
|
}
|
|
11444
12069
|
|
|
@@ -11533,15 +12158,17 @@ class PartialConnectionService {
|
|
|
11533
12158
|
/**
|
|
11534
12159
|
* Memoized factory function for ClientPartial instances.
|
|
11535
12160
|
*
|
|
11536
|
-
* Creates one ClientPartial per signal ID with configured callbacks.
|
|
12161
|
+
* Creates one ClientPartial per signal ID and backtest mode with configured callbacks.
|
|
11537
12162
|
* Instances are cached until clear() is called.
|
|
11538
12163
|
*
|
|
11539
|
-
* Key format: signalId
|
|
12164
|
+
* Key format: "signalId:backtest" or "signalId:live"
|
|
11540
12165
|
* Value: ClientPartial instance with logger and event emitters
|
|
11541
12166
|
*/
|
|
11542
|
-
this.getPartial = memoize(([signalId]) => `${signalId}`, () => {
|
|
12167
|
+
this.getPartial = memoize(([signalId, backtest]) => `${signalId}:${backtest ? "backtest" : "live"}`, (signalId, backtest) => {
|
|
11543
12168
|
return new ClientPartial({
|
|
12169
|
+
signalId,
|
|
11544
12170
|
logger: this.loggerService,
|
|
12171
|
+
backtest,
|
|
11545
12172
|
onProfit: COMMIT_PROFIT_FN,
|
|
11546
12173
|
onLoss: COMMIT_LOSS_FN,
|
|
11547
12174
|
});
|
|
@@ -11569,8 +12196,8 @@ class PartialConnectionService {
|
|
|
11569
12196
|
backtest,
|
|
11570
12197
|
when,
|
|
11571
12198
|
});
|
|
11572
|
-
const partial = this.getPartial(data.id);
|
|
11573
|
-
await partial.waitForInit(symbol);
|
|
12199
|
+
const partial = this.getPartial(data.id, backtest);
|
|
12200
|
+
await partial.waitForInit(symbol, data.strategyName);
|
|
11574
12201
|
return await partial.profit(symbol, data, currentPrice, revenuePercent, backtest, when);
|
|
11575
12202
|
};
|
|
11576
12203
|
/**
|
|
@@ -11596,8 +12223,8 @@ class PartialConnectionService {
|
|
|
11596
12223
|
backtest,
|
|
11597
12224
|
when,
|
|
11598
12225
|
});
|
|
11599
|
-
const partial = this.getPartial(data.id);
|
|
11600
|
-
await partial.waitForInit(symbol);
|
|
12226
|
+
const partial = this.getPartial(data.id, backtest);
|
|
12227
|
+
await partial.waitForInit(symbol, data.strategyName);
|
|
11601
12228
|
return await partial.loss(symbol, data, currentPrice, lossPercent, backtest, when);
|
|
11602
12229
|
};
|
|
11603
12230
|
/**
|
|
@@ -11618,75 +12245,21 @@ class PartialConnectionService {
|
|
|
11618
12245
|
* @returns Promise that resolves when clear is complete
|
|
11619
12246
|
*/
|
|
11620
12247
|
this.clear = async (symbol, data, priceClose, backtest) => {
|
|
11621
|
-
this.loggerService.log("partialConnectionService
|
|
12248
|
+
this.loggerService.log("partialConnectionService clear", {
|
|
11622
12249
|
symbol,
|
|
11623
12250
|
data,
|
|
11624
12251
|
priceClose,
|
|
12252
|
+
backtest,
|
|
11625
12253
|
});
|
|
11626
|
-
const partial = this.getPartial(data.id);
|
|
11627
|
-
await partial.waitForInit(symbol);
|
|
12254
|
+
const partial = this.getPartial(data.id, backtest);
|
|
12255
|
+
await partial.waitForInit(symbol, data.strategyName);
|
|
11628
12256
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
11629
|
-
|
|
12257
|
+
const key = `${data.id}:${backtest ? "backtest" : "live"}`;
|
|
12258
|
+
this.getPartial.clear(key);
|
|
11630
12259
|
};
|
|
11631
12260
|
}
|
|
11632
12261
|
}
|
|
11633
12262
|
|
|
11634
|
-
const columns$1 = [
|
|
11635
|
-
{
|
|
11636
|
-
key: "action",
|
|
11637
|
-
label: "Action",
|
|
11638
|
-
format: (data) => data.action.toUpperCase(),
|
|
11639
|
-
isVisible: () => true,
|
|
11640
|
-
},
|
|
11641
|
-
{
|
|
11642
|
-
key: "symbol",
|
|
11643
|
-
label: "Symbol",
|
|
11644
|
-
format: (data) => data.symbol,
|
|
11645
|
-
isVisible: () => true,
|
|
11646
|
-
},
|
|
11647
|
-
{
|
|
11648
|
-
key: "strategyName",
|
|
11649
|
-
label: "Strategy",
|
|
11650
|
-
format: (data) => data.strategyName,
|
|
11651
|
-
isVisible: () => true,
|
|
11652
|
-
},
|
|
11653
|
-
{
|
|
11654
|
-
key: "signalId",
|
|
11655
|
-
label: "Signal ID",
|
|
11656
|
-
format: (data) => data.signalId,
|
|
11657
|
-
isVisible: () => true,
|
|
11658
|
-
},
|
|
11659
|
-
{
|
|
11660
|
-
key: "position",
|
|
11661
|
-
label: "Position",
|
|
11662
|
-
format: (data) => data.position.toUpperCase(),
|
|
11663
|
-
isVisible: () => true,
|
|
11664
|
-
},
|
|
11665
|
-
{
|
|
11666
|
-
key: "level",
|
|
11667
|
-
label: "Level %",
|
|
11668
|
-
format: (data) => data.action === "profit" ? `+${data.level}%` : `-${data.level}%`,
|
|
11669
|
-
isVisible: () => true,
|
|
11670
|
-
},
|
|
11671
|
-
{
|
|
11672
|
-
key: "currentPrice",
|
|
11673
|
-
label: "Current Price",
|
|
11674
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
11675
|
-
isVisible: () => true,
|
|
11676
|
-
},
|
|
11677
|
-
{
|
|
11678
|
-
key: "timestamp",
|
|
11679
|
-
label: "Timestamp",
|
|
11680
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
11681
|
-
isVisible: () => true,
|
|
11682
|
-
},
|
|
11683
|
-
{
|
|
11684
|
-
key: "mode",
|
|
11685
|
-
label: "Mode",
|
|
11686
|
-
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
11687
|
-
isVisible: () => true,
|
|
11688
|
-
},
|
|
11689
|
-
];
|
|
11690
12263
|
/** Maximum number of events to store in partial reports */
|
|
11691
12264
|
const MAX_EVENTS$1 = 250;
|
|
11692
12265
|
/**
|
|
@@ -11776,9 +12349,10 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11776
12349
|
*
|
|
11777
12350
|
* @param symbol - Trading pair symbol
|
|
11778
12351
|
* @param strategyName - Strategy name
|
|
12352
|
+
* @param columns - Column configuration for formatting the table
|
|
11779
12353
|
* @returns Markdown formatted report with all events
|
|
11780
12354
|
*/
|
|
11781
|
-
async getReport(symbol, strategyName) {
|
|
12355
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) {
|
|
11782
12356
|
const stats = await this.getData();
|
|
11783
12357
|
if (stats.totalEvents === 0) {
|
|
11784
12358
|
return [
|
|
@@ -11787,10 +12361,15 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11787
12361
|
"No partial profit/loss events recorded yet."
|
|
11788
12362
|
].join("\n");
|
|
11789
12363
|
}
|
|
11790
|
-
const visibleColumns =
|
|
12364
|
+
const visibleColumns = [];
|
|
12365
|
+
for (const col of columns) {
|
|
12366
|
+
if (await col.isVisible()) {
|
|
12367
|
+
visibleColumns.push(col);
|
|
12368
|
+
}
|
|
12369
|
+
}
|
|
11791
12370
|
const header = visibleColumns.map((col) => col.label);
|
|
11792
12371
|
const separator = visibleColumns.map(() => "---");
|
|
11793
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
12372
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
11794
12373
|
const tableData = [header, separator, ...rows];
|
|
11795
12374
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
11796
12375
|
return [
|
|
@@ -11809,9 +12388,10 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11809
12388
|
* @param symbol - Trading pair symbol
|
|
11810
12389
|
* @param strategyName - Strategy name
|
|
11811
12390
|
* @param path - Directory path to save report (default: "./dump/partial")
|
|
12391
|
+
* @param columns - Column configuration for formatting the table
|
|
11812
12392
|
*/
|
|
11813
|
-
async dump(symbol, strategyName, path = "./dump/partial") {
|
|
11814
|
-
const markdown = await this.getReport(symbol, strategyName);
|
|
12393
|
+
async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
12394
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
11815
12395
|
try {
|
|
11816
12396
|
const dir = join(process.cwd(), path);
|
|
11817
12397
|
await mkdir(dir, { recursive: true });
|
|
@@ -11922,6 +12502,7 @@ class PartialMarkdownService {
|
|
|
11922
12502
|
*
|
|
11923
12503
|
* @param symbol - Trading pair symbol to generate report for
|
|
11924
12504
|
* @param strategyName - Strategy name to generate report for
|
|
12505
|
+
* @param columns - Column configuration for formatting the table
|
|
11925
12506
|
* @returns Markdown formatted report string with table of all events
|
|
11926
12507
|
*
|
|
11927
12508
|
* @example
|
|
@@ -11931,13 +12512,13 @@ class PartialMarkdownService {
|
|
|
11931
12512
|
* console.log(markdown);
|
|
11932
12513
|
* ```
|
|
11933
12514
|
*/
|
|
11934
|
-
this.getReport = async (symbol, strategyName) => {
|
|
12515
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) => {
|
|
11935
12516
|
this.loggerService.log("partialMarkdownService getReport", {
|
|
11936
12517
|
symbol,
|
|
11937
12518
|
strategyName,
|
|
11938
12519
|
});
|
|
11939
12520
|
const storage = this.getStorage(symbol, strategyName);
|
|
11940
|
-
return storage.getReport(symbol, strategyName);
|
|
12521
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
11941
12522
|
};
|
|
11942
12523
|
/**
|
|
11943
12524
|
* Saves symbol-strategy report to disk.
|
|
@@ -11947,6 +12528,7 @@ class PartialMarkdownService {
|
|
|
11947
12528
|
* @param symbol - Trading pair symbol to save report for
|
|
11948
12529
|
* @param strategyName - Strategy name to save report for
|
|
11949
12530
|
* @param path - Directory path to save report (default: "./dump/partial")
|
|
12531
|
+
* @param columns - Column configuration for formatting the table
|
|
11950
12532
|
*
|
|
11951
12533
|
* @example
|
|
11952
12534
|
* ```typescript
|
|
@@ -11959,14 +12541,14 @@ class PartialMarkdownService {
|
|
|
11959
12541
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
11960
12542
|
* ```
|
|
11961
12543
|
*/
|
|
11962
|
-
this.dump = async (symbol, strategyName, path = "./dump/partial") => {
|
|
12544
|
+
this.dump = async (symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) => {
|
|
11963
12545
|
this.loggerService.log("partialMarkdownService dump", {
|
|
11964
12546
|
symbol,
|
|
11965
12547
|
strategyName,
|
|
11966
12548
|
path,
|
|
11967
12549
|
});
|
|
11968
12550
|
const storage = this.getStorage(symbol, strategyName);
|
|
11969
|
-
await storage.dump(symbol, strategyName, path);
|
|
12551
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
11970
12552
|
};
|
|
11971
12553
|
/**
|
|
11972
12554
|
* Clears accumulated event data from storage.
|
|
@@ -12426,92 +13008,6 @@ class ConfigValidationService {
|
|
|
12426
13008
|
}
|
|
12427
13009
|
}
|
|
12428
13010
|
|
|
12429
|
-
const columns = [
|
|
12430
|
-
{
|
|
12431
|
-
key: "symbol",
|
|
12432
|
-
label: "Symbol",
|
|
12433
|
-
format: (data) => data.symbol,
|
|
12434
|
-
isVisible: () => true,
|
|
12435
|
-
},
|
|
12436
|
-
{
|
|
12437
|
-
key: "strategyName",
|
|
12438
|
-
label: "Strategy",
|
|
12439
|
-
format: (data) => data.strategyName,
|
|
12440
|
-
isVisible: () => true,
|
|
12441
|
-
},
|
|
12442
|
-
{
|
|
12443
|
-
key: "signalId",
|
|
12444
|
-
label: "Signal ID",
|
|
12445
|
-
format: (data) => data.pendingSignal.id || "N/A",
|
|
12446
|
-
isVisible: () => true,
|
|
12447
|
-
},
|
|
12448
|
-
{
|
|
12449
|
-
key: "position",
|
|
12450
|
-
label: "Position",
|
|
12451
|
-
format: (data) => data.pendingSignal.position.toUpperCase(),
|
|
12452
|
-
isVisible: () => true,
|
|
12453
|
-
},
|
|
12454
|
-
{
|
|
12455
|
-
key: "note",
|
|
12456
|
-
label: "Note",
|
|
12457
|
-
format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
|
|
12458
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
12459
|
-
},
|
|
12460
|
-
{
|
|
12461
|
-
key: "exchangeName",
|
|
12462
|
-
label: "Exchange",
|
|
12463
|
-
format: (data) => data.exchangeName,
|
|
12464
|
-
isVisible: () => true,
|
|
12465
|
-
},
|
|
12466
|
-
{
|
|
12467
|
-
key: "openPrice",
|
|
12468
|
-
label: "Open Price",
|
|
12469
|
-
format: (data) => data.pendingSignal.priceOpen !== undefined
|
|
12470
|
-
? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
|
|
12471
|
-
: "N/A",
|
|
12472
|
-
isVisible: () => true,
|
|
12473
|
-
},
|
|
12474
|
-
{
|
|
12475
|
-
key: "takeProfit",
|
|
12476
|
-
label: "Take Profit",
|
|
12477
|
-
format: (data) => data.pendingSignal.priceTakeProfit !== undefined
|
|
12478
|
-
? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
|
|
12479
|
-
: "N/A",
|
|
12480
|
-
isVisible: () => true,
|
|
12481
|
-
},
|
|
12482
|
-
{
|
|
12483
|
-
key: "stopLoss",
|
|
12484
|
-
label: "Stop Loss",
|
|
12485
|
-
format: (data) => data.pendingSignal.priceStopLoss !== undefined
|
|
12486
|
-
? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
|
|
12487
|
-
: "N/A",
|
|
12488
|
-
isVisible: () => true,
|
|
12489
|
-
},
|
|
12490
|
-
{
|
|
12491
|
-
key: "currentPrice",
|
|
12492
|
-
label: "Current Price",
|
|
12493
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
12494
|
-
isVisible: () => true,
|
|
12495
|
-
},
|
|
12496
|
-
{
|
|
12497
|
-
key: "activePositionCount",
|
|
12498
|
-
label: "Active Positions",
|
|
12499
|
-
format: (data) => data.activePositionCount.toString(),
|
|
12500
|
-
isVisible: () => true,
|
|
12501
|
-
},
|
|
12502
|
-
{
|
|
12503
|
-
key: "comment",
|
|
12504
|
-
label: "Reason",
|
|
12505
|
-
format: (data) => data.comment,
|
|
12506
|
-
isVisible: () => true,
|
|
12507
|
-
},
|
|
12508
|
-
{
|
|
12509
|
-
key: "timestamp",
|
|
12510
|
-
label: "Timestamp",
|
|
12511
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
12512
|
-
isVisible: () => true,
|
|
12513
|
-
},
|
|
12514
|
-
];
|
|
12515
13011
|
/** Maximum number of events to store in risk reports */
|
|
12516
13012
|
const MAX_EVENTS = 250;
|
|
12517
13013
|
/**
|
|
@@ -12567,9 +13063,10 @@ class ReportStorage {
|
|
|
12567
13063
|
*
|
|
12568
13064
|
* @param symbol - Trading pair symbol
|
|
12569
13065
|
* @param strategyName - Strategy name
|
|
13066
|
+
* @param columns - Column configuration for formatting the table
|
|
12570
13067
|
* @returns Markdown formatted report with all events
|
|
12571
13068
|
*/
|
|
12572
|
-
async getReport(symbol, strategyName) {
|
|
13069
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) {
|
|
12573
13070
|
const stats = await this.getData();
|
|
12574
13071
|
if (stats.totalRejections === 0) {
|
|
12575
13072
|
return [
|
|
@@ -12578,10 +13075,15 @@ class ReportStorage {
|
|
|
12578
13075
|
"No risk rejections recorded yet.",
|
|
12579
13076
|
].join("\n");
|
|
12580
13077
|
}
|
|
12581
|
-
const visibleColumns =
|
|
13078
|
+
const visibleColumns = [];
|
|
13079
|
+
for (const col of columns) {
|
|
13080
|
+
if (await col.isVisible()) {
|
|
13081
|
+
visibleColumns.push(col);
|
|
13082
|
+
}
|
|
13083
|
+
}
|
|
12582
13084
|
const header = visibleColumns.map((col) => col.label);
|
|
12583
13085
|
const separator = visibleColumns.map(() => "---");
|
|
12584
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
13086
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
12585
13087
|
const tableData = [header, separator, ...rows];
|
|
12586
13088
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
12587
13089
|
return [
|
|
@@ -12604,9 +13106,10 @@ class ReportStorage {
|
|
|
12604
13106
|
* @param symbol - Trading pair symbol
|
|
12605
13107
|
* @param strategyName - Strategy name
|
|
12606
13108
|
* @param path - Directory path to save report (default: "./dump/risk")
|
|
13109
|
+
* @param columns - Column configuration for formatting the table
|
|
12607
13110
|
*/
|
|
12608
|
-
async dump(symbol, strategyName, path = "./dump/risk") {
|
|
12609
|
-
const markdown = await this.getReport(symbol, strategyName);
|
|
13111
|
+
async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
13112
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
12610
13113
|
try {
|
|
12611
13114
|
const dir = join(process.cwd(), path);
|
|
12612
13115
|
await mkdir(dir, { recursive: true });
|
|
@@ -12698,6 +13201,7 @@ class RiskMarkdownService {
|
|
|
12698
13201
|
*
|
|
12699
13202
|
* @param symbol - Trading pair symbol to generate report for
|
|
12700
13203
|
* @param strategyName - Strategy name to generate report for
|
|
13204
|
+
* @param columns - Column configuration for formatting the table
|
|
12701
13205
|
* @returns Markdown formatted report string with table of all events
|
|
12702
13206
|
*
|
|
12703
13207
|
* @example
|
|
@@ -12707,13 +13211,13 @@ class RiskMarkdownService {
|
|
|
12707
13211
|
* console.log(markdown);
|
|
12708
13212
|
* ```
|
|
12709
13213
|
*/
|
|
12710
|
-
this.getReport = async (symbol, strategyName) => {
|
|
13214
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) => {
|
|
12711
13215
|
this.loggerService.log("riskMarkdownService getReport", {
|
|
12712
13216
|
symbol,
|
|
12713
13217
|
strategyName,
|
|
12714
13218
|
});
|
|
12715
13219
|
const storage = this.getStorage(symbol, strategyName);
|
|
12716
|
-
return storage.getReport(symbol, strategyName);
|
|
13220
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
12717
13221
|
};
|
|
12718
13222
|
/**
|
|
12719
13223
|
* Saves symbol-strategy report to disk.
|
|
@@ -12723,6 +13227,7 @@ class RiskMarkdownService {
|
|
|
12723
13227
|
* @param symbol - Trading pair symbol to save report for
|
|
12724
13228
|
* @param strategyName - Strategy name to save report for
|
|
12725
13229
|
* @param path - Directory path to save report (default: "./dump/risk")
|
|
13230
|
+
* @param columns - Column configuration for formatting the table
|
|
12726
13231
|
*
|
|
12727
13232
|
* @example
|
|
12728
13233
|
* ```typescript
|
|
@@ -12735,14 +13240,14 @@ class RiskMarkdownService {
|
|
|
12735
13240
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
12736
13241
|
* ```
|
|
12737
13242
|
*/
|
|
12738
|
-
this.dump = async (symbol, strategyName, path = "./dump/risk") => {
|
|
13243
|
+
this.dump = async (symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) => {
|
|
12739
13244
|
this.loggerService.log("riskMarkdownService dump", {
|
|
12740
13245
|
symbol,
|
|
12741
13246
|
strategyName,
|
|
12742
13247
|
path,
|
|
12743
13248
|
});
|
|
12744
13249
|
const storage = this.getStorage(symbol, strategyName);
|
|
12745
|
-
await storage.dump(symbol, strategyName, path);
|
|
13250
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
12746
13251
|
};
|
|
12747
13252
|
/**
|
|
12748
13253
|
* Clears accumulated event data from storage.
|
|
@@ -12792,6 +13297,118 @@ class RiskMarkdownService {
|
|
|
12792
13297
|
}
|
|
12793
13298
|
}
|
|
12794
13299
|
|
|
13300
|
+
/**
|
|
13301
|
+
* Service for validating column configurations to ensure consistency with ColumnModel interface
|
|
13302
|
+
* and prevent invalid column definitions.
|
|
13303
|
+
*
|
|
13304
|
+
* Performs comprehensive validation on all column definitions in COLUMN_CONFIG:
|
|
13305
|
+
* - **Required fields**: All columns must have key, label, format, and isVisible properties
|
|
13306
|
+
* - **Unique keys**: All key values must be unique within each column collection
|
|
13307
|
+
* - **Function validation**: format and isVisible must be callable functions
|
|
13308
|
+
* - **Data types**: key and label must be non-empty strings
|
|
13309
|
+
*
|
|
13310
|
+
* @throws {Error} If any validation fails, throws with detailed breakdown of all errors
|
|
13311
|
+
*
|
|
13312
|
+
* @example
|
|
13313
|
+
* ```typescript
|
|
13314
|
+
* const validator = new ColumnValidationService();
|
|
13315
|
+
* validator.validate(); // Throws if column configuration is invalid
|
|
13316
|
+
* ```
|
|
13317
|
+
*
|
|
13318
|
+
* @example Validation failure output:
|
|
13319
|
+
* ```
|
|
13320
|
+
* Column configuration validation failed:
|
|
13321
|
+
* 1. backtest_columns[0]: Missing required field "format"
|
|
13322
|
+
* 2. heat_columns: Duplicate key "symbol" at indexes 1, 5
|
|
13323
|
+
* 3. live_columns[3].isVisible must be a function, got "boolean"
|
|
13324
|
+
* ```
|
|
13325
|
+
*/
|
|
13326
|
+
class ColumnValidationService {
|
|
13327
|
+
constructor() {
|
|
13328
|
+
/**
|
|
13329
|
+
* @private
|
|
13330
|
+
* @readonly
|
|
13331
|
+
* Injected logger service instance
|
|
13332
|
+
*/
|
|
13333
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
13334
|
+
/**
|
|
13335
|
+
* Validates all column configurations in COLUMN_CONFIG for structural correctness.
|
|
13336
|
+
*
|
|
13337
|
+
* Checks:
|
|
13338
|
+
* 1. All required fields (key, label, format, isVisible) are present in each column
|
|
13339
|
+
* 2. key and label are non-empty strings
|
|
13340
|
+
* 3. format and isVisible are functions (not other types)
|
|
13341
|
+
* 4. All keys are unique within each column collection
|
|
13342
|
+
*
|
|
13343
|
+
* @throws Error if configuration is invalid
|
|
13344
|
+
*/
|
|
13345
|
+
this.validate = () => {
|
|
13346
|
+
this.loggerService.log("columnValidationService validate");
|
|
13347
|
+
const errors = [];
|
|
13348
|
+
// Iterate through all column collections in COLUMN_CONFIG
|
|
13349
|
+
for (const [configKey, columns] of Object.entries(COLUMN_CONFIG)) {
|
|
13350
|
+
if (!Array.isArray(columns)) {
|
|
13351
|
+
errors.push(`${configKey} is not an array, got ${typeof columns}`);
|
|
13352
|
+
continue;
|
|
13353
|
+
}
|
|
13354
|
+
// Track keys for uniqueness check
|
|
13355
|
+
const keyMap = new Map();
|
|
13356
|
+
// Validate each column in the collection
|
|
13357
|
+
columns.forEach((column, index) => {
|
|
13358
|
+
if (!column || typeof column !== "object") {
|
|
13359
|
+
errors.push(`${configKey}[${index}]: Column must be an object, got ${typeof column}`);
|
|
13360
|
+
return;
|
|
13361
|
+
}
|
|
13362
|
+
// Check for all required fields
|
|
13363
|
+
const requiredFields = ["key", "label", "format", "isVisible"];
|
|
13364
|
+
for (const field of requiredFields) {
|
|
13365
|
+
if (!(field in column)) {
|
|
13366
|
+
errors.push(`${configKey}[${index}]: Missing required field "${field}"`);
|
|
13367
|
+
}
|
|
13368
|
+
}
|
|
13369
|
+
// Validate key and label are non-empty strings
|
|
13370
|
+
if (typeof column.key !== "string" || column.key.trim() === "") {
|
|
13371
|
+
errors.push(`${configKey}[${index}].key must be a non-empty string, got ${typeof column.key === "string" ? `"${column.key}"` : typeof column.key}`);
|
|
13372
|
+
}
|
|
13373
|
+
else {
|
|
13374
|
+
// Track key for uniqueness check
|
|
13375
|
+
if (!keyMap.has(column.key)) {
|
|
13376
|
+
keyMap.set(column.key, []);
|
|
13377
|
+
}
|
|
13378
|
+
keyMap.get(column.key).push(index);
|
|
13379
|
+
}
|
|
13380
|
+
if (typeof column.label !== "string" || column.label.trim() === "") {
|
|
13381
|
+
errors.push(`${configKey}[${index}].label must be a non-empty string, got ${typeof column.label === "string" ? `"${column.label}"` : typeof column.label}`);
|
|
13382
|
+
}
|
|
13383
|
+
// Validate format is a function
|
|
13384
|
+
if (typeof column.format !== "function") {
|
|
13385
|
+
errors.push(`${configKey}[${index}].format must be a function, got "${typeof column.format}"`);
|
|
13386
|
+
}
|
|
13387
|
+
// Validate isVisible is a function
|
|
13388
|
+
if (typeof column.isVisible !== "function") {
|
|
13389
|
+
errors.push(`${configKey}[${index}].isVisible must be a function, got "${typeof column.isVisible}"`);
|
|
13390
|
+
}
|
|
13391
|
+
});
|
|
13392
|
+
// Check for duplicate keys
|
|
13393
|
+
for (const [key, indexes] of keyMap.entries()) {
|
|
13394
|
+
if (indexes.length > 1) {
|
|
13395
|
+
errors.push(`${configKey}: Duplicate key "${key}" at indexes ${indexes.join(", ")}`);
|
|
13396
|
+
}
|
|
13397
|
+
}
|
|
13398
|
+
}
|
|
13399
|
+
// Throw aggregated errors if any
|
|
13400
|
+
if (errors.length > 0) {
|
|
13401
|
+
const errorMessage = `Column configuration validation failed:\n${errors
|
|
13402
|
+
.map((e, i) => ` ${i + 1}. ${e}`)
|
|
13403
|
+
.join("\n")}`;
|
|
13404
|
+
this.loggerService.warn(errorMessage);
|
|
13405
|
+
throw new Error(errorMessage);
|
|
13406
|
+
}
|
|
13407
|
+
this.loggerService.log("columnValidationService validation passed");
|
|
13408
|
+
};
|
|
13409
|
+
}
|
|
13410
|
+
}
|
|
13411
|
+
|
|
12795
13412
|
{
|
|
12796
13413
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
12797
13414
|
}
|
|
@@ -12863,6 +13480,7 @@ class RiskMarkdownService {
|
|
|
12863
13480
|
provide(TYPES.riskValidationService, () => new RiskValidationService());
|
|
12864
13481
|
provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
|
|
12865
13482
|
provide(TYPES.configValidationService, () => new ConfigValidationService());
|
|
13483
|
+
provide(TYPES.columnValidationService, () => new ColumnValidationService());
|
|
12866
13484
|
}
|
|
12867
13485
|
{
|
|
12868
13486
|
provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
|
|
@@ -12939,6 +13557,7 @@ const validationServices = {
|
|
|
12939
13557
|
riskValidationService: inject(TYPES.riskValidationService),
|
|
12940
13558
|
optimizerValidationService: inject(TYPES.optimizerValidationService),
|
|
12941
13559
|
configValidationService: inject(TYPES.configValidationService),
|
|
13560
|
+
columnValidationService: inject(TYPES.columnValidationService),
|
|
12942
13561
|
};
|
|
12943
13562
|
const templateServices = {
|
|
12944
13563
|
optimizerTemplateService: inject(TYPES.optimizerTemplateService),
|
|
@@ -13038,6 +13657,77 @@ function getConfig() {
|
|
|
13038
13657
|
function getDefaultConfig() {
|
|
13039
13658
|
return DEFAULT_CONFIG;
|
|
13040
13659
|
}
|
|
13660
|
+
/**
|
|
13661
|
+
* Sets custom column configurations for markdown report generation.
|
|
13662
|
+
*
|
|
13663
|
+
* Allows overriding default column definitions for any report type.
|
|
13664
|
+
* All columns are validated before assignment to ensure structural correctness.
|
|
13665
|
+
*
|
|
13666
|
+
* @param columns - Partial column configuration object to override default column settings
|
|
13667
|
+
* @param _unsafe - Skip column validations - required for testbed
|
|
13668
|
+
*
|
|
13669
|
+
* @example
|
|
13670
|
+
* ```typescript
|
|
13671
|
+
* setColumns({
|
|
13672
|
+
* backtest_columns: [
|
|
13673
|
+
* {
|
|
13674
|
+
* key: "customId",
|
|
13675
|
+
* label: "Custom ID",
|
|
13676
|
+
* format: (data) => data.signal.id,
|
|
13677
|
+
* isVisible: () => true
|
|
13678
|
+
* }
|
|
13679
|
+
* ],
|
|
13680
|
+
* });
|
|
13681
|
+
* ```
|
|
13682
|
+
*
|
|
13683
|
+
* @throws {Error} If column configuration is invalid
|
|
13684
|
+
*/
|
|
13685
|
+
function setColumns(columns, _unsafe) {
|
|
13686
|
+
const prevConfig = Object.assign({}, COLUMN_CONFIG);
|
|
13687
|
+
try {
|
|
13688
|
+
Object.assign(COLUMN_CONFIG, columns);
|
|
13689
|
+
!_unsafe && backtest$1.columnValidationService.validate();
|
|
13690
|
+
}
|
|
13691
|
+
catch (error) {
|
|
13692
|
+
console.warn(`backtest-kit setColumns failed: ${getErrorMessage(error)}`, columns);
|
|
13693
|
+
Object.assign(COLUMN_CONFIG, prevConfig);
|
|
13694
|
+
throw error;
|
|
13695
|
+
}
|
|
13696
|
+
}
|
|
13697
|
+
/**
|
|
13698
|
+
* Retrieves a copy of the current column configuration for markdown report generation.
|
|
13699
|
+
*
|
|
13700
|
+
* Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
|
|
13701
|
+
* Use this to inspect the current column definitions without modifying them.
|
|
13702
|
+
*
|
|
13703
|
+
* @returns {ColumnConfig} A copy of the current column configuration object
|
|
13704
|
+
*
|
|
13705
|
+
* @example
|
|
13706
|
+
* ```typescript
|
|
13707
|
+
* const currentColumns = getColumns();
|
|
13708
|
+
* console.log(currentColumns.backtest_columns.length);
|
|
13709
|
+
* ```
|
|
13710
|
+
*/
|
|
13711
|
+
function getColumns() {
|
|
13712
|
+
return Object.assign({}, COLUMN_CONFIG);
|
|
13713
|
+
}
|
|
13714
|
+
/**
|
|
13715
|
+
* Retrieves the default column configuration object for markdown report generation.
|
|
13716
|
+
*
|
|
13717
|
+
* Returns a reference to the default column definitions with all preset values.
|
|
13718
|
+
* Use this to see what column options are available and their default definitions.
|
|
13719
|
+
*
|
|
13720
|
+
* @returns {ColumnConfig} The default column configuration object
|
|
13721
|
+
*
|
|
13722
|
+
* @example
|
|
13723
|
+
* ```typescript
|
|
13724
|
+
* const defaultColumns = getDefaultColumns();
|
|
13725
|
+
* console.log(defaultColumns.backtest_columns);
|
|
13726
|
+
* ```
|
|
13727
|
+
*/
|
|
13728
|
+
function getDefaultColumns() {
|
|
13729
|
+
return DEFAULT_COLUMNS;
|
|
13730
|
+
}
|
|
13041
13731
|
|
|
13042
13732
|
const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
|
|
13043
13733
|
const ADD_EXCHANGE_METHOD_NAME = "add.addExchange";
|
|
@@ -14917,12 +15607,12 @@ class BacktestInstance {
|
|
|
14917
15607
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
14918
15608
|
}
|
|
14919
15609
|
{
|
|
14920
|
-
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
15610
|
+
backtest$1.strategyCoreService.clear(true, { symbol, strategyName: context.strategyName });
|
|
14921
15611
|
}
|
|
14922
15612
|
{
|
|
14923
15613
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
14924
|
-
riskName && backtest$1.riskGlobalService.clear(riskName);
|
|
14925
|
-
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(riskName));
|
|
15614
|
+
riskName && backtest$1.riskGlobalService.clear(true, riskName);
|
|
15615
|
+
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(true, riskName));
|
|
14926
15616
|
}
|
|
14927
15617
|
return backtest$1.backtestCommandService.run(symbol, context);
|
|
14928
15618
|
};
|
|
@@ -14953,9 +15643,9 @@ class BacktestInstance {
|
|
|
14953
15643
|
});
|
|
14954
15644
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
14955
15645
|
return () => {
|
|
14956
|
-
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }
|
|
15646
|
+
backtest$1.strategyCoreService.stop(true, { symbol, strategyName: context.strategyName });
|
|
14957
15647
|
backtest$1.strategyCoreService
|
|
14958
|
-
.getPendingSignal(symbol, context.strategyName)
|
|
15648
|
+
.getPendingSignal(true, symbol, context.strategyName)
|
|
14959
15649
|
.then(async (pendingSignal) => {
|
|
14960
15650
|
if (pendingSignal) {
|
|
14961
15651
|
return;
|
|
@@ -14995,7 +15685,7 @@ class BacktestInstance {
|
|
|
14995
15685
|
symbol,
|
|
14996
15686
|
strategyName,
|
|
14997
15687
|
});
|
|
14998
|
-
await backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
15688
|
+
await backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
|
|
14999
15689
|
};
|
|
15000
15690
|
/**
|
|
15001
15691
|
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
@@ -15023,6 +15713,7 @@ class BacktestInstance {
|
|
|
15023
15713
|
*
|
|
15024
15714
|
* @param symbol - Trading pair symbol
|
|
15025
15715
|
* @param strategyName - Strategy name to generate report for
|
|
15716
|
+
* @param columns - Optional columns configuration for the report
|
|
15026
15717
|
* @returns Promise resolving to markdown formatted report string
|
|
15027
15718
|
*
|
|
15028
15719
|
* @example
|
|
@@ -15032,12 +15723,12 @@ class BacktestInstance {
|
|
|
15032
15723
|
* console.log(markdown);
|
|
15033
15724
|
* ```
|
|
15034
15725
|
*/
|
|
15035
|
-
this.getReport = async (symbol, strategyName) => {
|
|
15726
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15036
15727
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_REPORT, {
|
|
15037
15728
|
symbol,
|
|
15038
15729
|
strategyName,
|
|
15039
15730
|
});
|
|
15040
|
-
return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName);
|
|
15731
|
+
return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName, columns);
|
|
15041
15732
|
};
|
|
15042
15733
|
/**
|
|
15043
15734
|
* Saves strategy report to disk.
|
|
@@ -15045,6 +15736,7 @@ class BacktestInstance {
|
|
|
15045
15736
|
* @param symbol - Trading pair symbol
|
|
15046
15737
|
* @param strategyName - Strategy name to save report for
|
|
15047
15738
|
* @param path - Optional directory path to save report (default: "./dump/backtest")
|
|
15739
|
+
* @param columns - Optional columns configuration for the report
|
|
15048
15740
|
*
|
|
15049
15741
|
* @example
|
|
15050
15742
|
* ```typescript
|
|
@@ -15056,13 +15748,13 @@ class BacktestInstance {
|
|
|
15056
15748
|
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15057
15749
|
* ```
|
|
15058
15750
|
*/
|
|
15059
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
15751
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15060
15752
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_DUMP, {
|
|
15061
15753
|
symbol,
|
|
15062
15754
|
strategyName,
|
|
15063
15755
|
path,
|
|
15064
15756
|
});
|
|
15065
|
-
await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path);
|
|
15757
|
+
await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15066
15758
|
};
|
|
15067
15759
|
}
|
|
15068
15760
|
}
|
|
@@ -15201,6 +15893,7 @@ class BacktestUtils {
|
|
|
15201
15893
|
*
|
|
15202
15894
|
* @param symbol - Trading pair symbol
|
|
15203
15895
|
* @param strategyName - Strategy name to generate report for
|
|
15896
|
+
* @param columns - Optional columns configuration for the report
|
|
15204
15897
|
* @returns Promise resolving to markdown formatted report string
|
|
15205
15898
|
*
|
|
15206
15899
|
* @example
|
|
@@ -15209,7 +15902,7 @@ class BacktestUtils {
|
|
|
15209
15902
|
* console.log(markdown);
|
|
15210
15903
|
* ```
|
|
15211
15904
|
*/
|
|
15212
|
-
this.getReport = async (symbol, strategyName) => {
|
|
15905
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15213
15906
|
backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
|
|
15214
15907
|
{
|
|
15215
15908
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15217,7 +15910,7 @@ class BacktestUtils {
|
|
|
15217
15910
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_REPORT));
|
|
15218
15911
|
}
|
|
15219
15912
|
const instance = this._getInstance(symbol, strategyName);
|
|
15220
|
-
return await instance.getReport(symbol, strategyName);
|
|
15913
|
+
return await instance.getReport(symbol, strategyName, columns);
|
|
15221
15914
|
};
|
|
15222
15915
|
/**
|
|
15223
15916
|
* Saves strategy report to disk.
|
|
@@ -15225,6 +15918,7 @@ class BacktestUtils {
|
|
|
15225
15918
|
* @param symbol - Trading pair symbol
|
|
15226
15919
|
* @param strategyName - Strategy name to save report for
|
|
15227
15920
|
* @param path - Optional directory path to save report (default: "./dump/backtest")
|
|
15921
|
+
* @param columns - Optional columns configuration for the report
|
|
15228
15922
|
*
|
|
15229
15923
|
* @example
|
|
15230
15924
|
* ```typescript
|
|
@@ -15235,7 +15929,7 @@ class BacktestUtils {
|
|
|
15235
15929
|
* await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15236
15930
|
* ```
|
|
15237
15931
|
*/
|
|
15238
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
15932
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15239
15933
|
backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_DUMP);
|
|
15240
15934
|
{
|
|
15241
15935
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15243,7 +15937,7 @@ class BacktestUtils {
|
|
|
15243
15937
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_DUMP));
|
|
15244
15938
|
}
|
|
15245
15939
|
const instance = this._getInstance(symbol, strategyName);
|
|
15246
|
-
return await instance.dump(symbol, strategyName, path);
|
|
15940
|
+
return await instance.dump(symbol, strategyName, path, columns);
|
|
15247
15941
|
};
|
|
15248
15942
|
/**
|
|
15249
15943
|
* Lists all active backtest instances with their current status.
|
|
@@ -15417,12 +16111,12 @@ class LiveInstance {
|
|
|
15417
16111
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
15418
16112
|
}
|
|
15419
16113
|
{
|
|
15420
|
-
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
16114
|
+
backtest$1.strategyCoreService.clear(false, { symbol, strategyName: context.strategyName });
|
|
15421
16115
|
}
|
|
15422
16116
|
{
|
|
15423
16117
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
15424
|
-
riskName && backtest$1.riskGlobalService.clear(riskName);
|
|
15425
|
-
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(riskName));
|
|
16118
|
+
riskName && backtest$1.riskGlobalService.clear(false, riskName);
|
|
16119
|
+
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(false, riskName));
|
|
15426
16120
|
}
|
|
15427
16121
|
return backtest$1.liveCommandService.run(symbol, context);
|
|
15428
16122
|
};
|
|
@@ -15453,9 +16147,9 @@ class LiveInstance {
|
|
|
15453
16147
|
});
|
|
15454
16148
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
15455
16149
|
return () => {
|
|
15456
|
-
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }
|
|
16150
|
+
backtest$1.strategyCoreService.stop(false, { symbol, strategyName: context.strategyName });
|
|
15457
16151
|
backtest$1.strategyCoreService
|
|
15458
|
-
.getPendingSignal(symbol, context.strategyName)
|
|
16152
|
+
.getPendingSignal(false, symbol, context.strategyName)
|
|
15459
16153
|
.then(async (pendingSignal) => {
|
|
15460
16154
|
if (pendingSignal) {
|
|
15461
16155
|
return;
|
|
@@ -15495,7 +16189,7 @@ class LiveInstance {
|
|
|
15495
16189
|
symbol,
|
|
15496
16190
|
strategyName,
|
|
15497
16191
|
});
|
|
15498
|
-
await backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
16192
|
+
await backtest$1.strategyCoreService.stop(false, { symbol, strategyName });
|
|
15499
16193
|
};
|
|
15500
16194
|
/**
|
|
15501
16195
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
@@ -15523,6 +16217,7 @@ class LiveInstance {
|
|
|
15523
16217
|
*
|
|
15524
16218
|
* @param symbol - Trading pair symbol
|
|
15525
16219
|
* @param strategyName - Strategy name to generate report for
|
|
16220
|
+
* @param columns - Optional columns configuration for the report
|
|
15526
16221
|
* @returns Promise resolving to markdown formatted report string
|
|
15527
16222
|
*
|
|
15528
16223
|
* @example
|
|
@@ -15532,12 +16227,12 @@ class LiveInstance {
|
|
|
15532
16227
|
* console.log(markdown);
|
|
15533
16228
|
* ```
|
|
15534
16229
|
*/
|
|
15535
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16230
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15536
16231
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
|
|
15537
16232
|
symbol,
|
|
15538
16233
|
strategyName,
|
|
15539
16234
|
});
|
|
15540
|
-
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
|
|
16235
|
+
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName, columns);
|
|
15541
16236
|
};
|
|
15542
16237
|
/**
|
|
15543
16238
|
* Saves strategy report to disk.
|
|
@@ -15545,6 +16240,7 @@ class LiveInstance {
|
|
|
15545
16240
|
* @param symbol - Trading pair symbol
|
|
15546
16241
|
* @param strategyName - Strategy name to save report for
|
|
15547
16242
|
* @param path - Optional directory path to save report (default: "./dump/live")
|
|
16243
|
+
* @param columns - Optional columns configuration for the report
|
|
15548
16244
|
*
|
|
15549
16245
|
* @example
|
|
15550
16246
|
* ```typescript
|
|
@@ -15556,13 +16252,13 @@ class LiveInstance {
|
|
|
15556
16252
|
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15557
16253
|
* ```
|
|
15558
16254
|
*/
|
|
15559
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16255
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15560
16256
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
|
|
15561
16257
|
symbol,
|
|
15562
16258
|
strategyName,
|
|
15563
16259
|
path,
|
|
15564
16260
|
});
|
|
15565
|
-
await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
|
|
16261
|
+
await backtest$1.liveMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15566
16262
|
};
|
|
15567
16263
|
}
|
|
15568
16264
|
}
|
|
@@ -15712,6 +16408,7 @@ class LiveUtils {
|
|
|
15712
16408
|
*
|
|
15713
16409
|
* @param symbol - Trading pair symbol
|
|
15714
16410
|
* @param strategyName - Strategy name to generate report for
|
|
16411
|
+
* @param columns - Optional columns configuration for the report
|
|
15715
16412
|
* @returns Promise resolving to markdown formatted report string
|
|
15716
16413
|
*
|
|
15717
16414
|
* @example
|
|
@@ -15720,7 +16417,7 @@ class LiveUtils {
|
|
|
15720
16417
|
* console.log(markdown);
|
|
15721
16418
|
* ```
|
|
15722
16419
|
*/
|
|
15723
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16420
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15724
16421
|
backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_GET_REPORT);
|
|
15725
16422
|
{
|
|
15726
16423
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15728,7 +16425,7 @@ class LiveUtils {
|
|
|
15728
16425
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
|
|
15729
16426
|
}
|
|
15730
16427
|
const instance = this._getInstance(symbol, strategyName);
|
|
15731
|
-
return await instance.getReport(symbol, strategyName);
|
|
16428
|
+
return await instance.getReport(symbol, strategyName, columns);
|
|
15732
16429
|
};
|
|
15733
16430
|
/**
|
|
15734
16431
|
* Saves strategy report to disk.
|
|
@@ -15736,6 +16433,7 @@ class LiveUtils {
|
|
|
15736
16433
|
* @param symbol - Trading pair symbol
|
|
15737
16434
|
* @param strategyName - Strategy name to save report for
|
|
15738
16435
|
* @param path - Optional directory path to save report (default: "./dump/live")
|
|
16436
|
+
* @param columns - Optional columns configuration for the report
|
|
15739
16437
|
*
|
|
15740
16438
|
* @example
|
|
15741
16439
|
* ```typescript
|
|
@@ -15746,7 +16444,7 @@ class LiveUtils {
|
|
|
15746
16444
|
* await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15747
16445
|
* ```
|
|
15748
16446
|
*/
|
|
15749
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16447
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15750
16448
|
backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_DUMP);
|
|
15751
16449
|
{
|
|
15752
16450
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15754,7 +16452,7 @@ class LiveUtils {
|
|
|
15754
16452
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
|
|
15755
16453
|
}
|
|
15756
16454
|
const instance = this._getInstance(symbol, strategyName);
|
|
15757
|
-
return await instance.dump(symbol, strategyName, path);
|
|
16455
|
+
return await instance.dump(symbol, strategyName, path, columns);
|
|
15758
16456
|
};
|
|
15759
16457
|
/**
|
|
15760
16458
|
* Lists all active live trading instances with their current status.
|
|
@@ -15853,6 +16551,7 @@ class ScheduleUtils {
|
|
|
15853
16551
|
*
|
|
15854
16552
|
* @param symbol - Trading pair symbol
|
|
15855
16553
|
* @param strategyName - Strategy name to generate report for
|
|
16554
|
+
* @param columns - Optional columns configuration for the report
|
|
15856
16555
|
* @returns Promise resolving to markdown formatted report string
|
|
15857
16556
|
*
|
|
15858
16557
|
* @example
|
|
@@ -15861,7 +16560,7 @@ class ScheduleUtils {
|
|
|
15861
16560
|
* console.log(markdown);
|
|
15862
16561
|
* ```
|
|
15863
16562
|
*/
|
|
15864
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16563
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15865
16564
|
backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_REPORT, {
|
|
15866
16565
|
symbol,
|
|
15867
16566
|
strategyName,
|
|
@@ -15872,7 +16571,7 @@ class ScheduleUtils {
|
|
|
15872
16571
|
riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
|
|
15873
16572
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT));
|
|
15874
16573
|
}
|
|
15875
|
-
return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName);
|
|
16574
|
+
return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName, columns);
|
|
15876
16575
|
};
|
|
15877
16576
|
/**
|
|
15878
16577
|
* Saves strategy report to disk.
|
|
@@ -15880,6 +16579,7 @@ class ScheduleUtils {
|
|
|
15880
16579
|
* @param symbol - Trading pair symbol
|
|
15881
16580
|
* @param strategyName - Strategy name to save report for
|
|
15882
16581
|
* @param path - Optional directory path to save report (default: "./dump/schedule")
|
|
16582
|
+
* @param columns - Optional columns configuration for the report
|
|
15883
16583
|
*
|
|
15884
16584
|
* @example
|
|
15885
16585
|
* ```typescript
|
|
@@ -15890,7 +16590,7 @@ class ScheduleUtils {
|
|
|
15890
16590
|
* await Schedule.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15891
16591
|
* ```
|
|
15892
16592
|
*/
|
|
15893
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16593
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15894
16594
|
backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_DUMP, {
|
|
15895
16595
|
symbol,
|
|
15896
16596
|
strategyName,
|
|
@@ -15902,7 +16602,7 @@ class ScheduleUtils {
|
|
|
15902
16602
|
riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
|
|
15903
16603
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP));
|
|
15904
16604
|
}
|
|
15905
|
-
await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path);
|
|
16605
|
+
await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15906
16606
|
};
|
|
15907
16607
|
}
|
|
15908
16608
|
}
|
|
@@ -16002,6 +16702,7 @@ class Performance {
|
|
|
16002
16702
|
*
|
|
16003
16703
|
* @param symbol - Trading pair symbol
|
|
16004
16704
|
* @param strategyName - Strategy name to generate report for
|
|
16705
|
+
* @param columns - Optional columns configuration for the report
|
|
16005
16706
|
* @returns Markdown formatted report string
|
|
16006
16707
|
*
|
|
16007
16708
|
* @example
|
|
@@ -16014,14 +16715,14 @@ class Performance {
|
|
|
16014
16715
|
* await fs.writeFile("performance-report.md", markdown);
|
|
16015
16716
|
* ```
|
|
16016
16717
|
*/
|
|
16017
|
-
static async getReport(symbol, strategyName) {
|
|
16718
|
+
static async getReport(symbol, strategyName, columns) {
|
|
16018
16719
|
backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
16019
16720
|
{
|
|
16020
16721
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16021
16722
|
riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
16022
16723
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT));
|
|
16023
16724
|
}
|
|
16024
|
-
return backtest$1.performanceMarkdownService.getReport(symbol, strategyName);
|
|
16725
|
+
return backtest$1.performanceMarkdownService.getReport(symbol, strategyName, columns);
|
|
16025
16726
|
}
|
|
16026
16727
|
/**
|
|
16027
16728
|
* Saves performance report to disk.
|
|
@@ -16032,6 +16733,7 @@ class Performance {
|
|
|
16032
16733
|
* @param symbol - Trading pair symbol
|
|
16033
16734
|
* @param strategyName - Strategy name to save report for
|
|
16034
16735
|
* @param path - Optional custom directory path
|
|
16736
|
+
* @param columns - Optional columns configuration for the report
|
|
16035
16737
|
*
|
|
16036
16738
|
* @example
|
|
16037
16739
|
* ```typescript
|
|
@@ -16042,14 +16744,14 @@ class Performance {
|
|
|
16042
16744
|
* await Performance.dump("BTCUSDT", "my-strategy", "./reports/perf");
|
|
16043
16745
|
* ```
|
|
16044
16746
|
*/
|
|
16045
|
-
static async dump(symbol, strategyName, path = "./dump/performance") {
|
|
16747
|
+
static async dump(symbol, strategyName, path = "./dump/performance", columns) {
|
|
16046
16748
|
backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
16047
16749
|
{
|
|
16048
16750
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16049
16751
|
riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
16050
16752
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP));
|
|
16051
16753
|
}
|
|
16052
|
-
return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path);
|
|
16754
|
+
return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path, columns);
|
|
16053
16755
|
}
|
|
16054
16756
|
}
|
|
16055
16757
|
|
|
@@ -16191,12 +16893,13 @@ class WalkerInstance {
|
|
|
16191
16893
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
|
|
16192
16894
|
}
|
|
16193
16895
|
{
|
|
16194
|
-
backtest$1.strategyCoreService.clear({ symbol, strategyName });
|
|
16896
|
+
backtest$1.strategyCoreService.clear(true, { symbol, strategyName });
|
|
16195
16897
|
}
|
|
16196
16898
|
{
|
|
16197
16899
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16198
|
-
riskName && backtest$1.riskGlobalService.clear(riskName);
|
|
16199
|
-
riskList &&
|
|
16900
|
+
riskName && backtest$1.riskGlobalService.clear(true, riskName);
|
|
16901
|
+
riskList &&
|
|
16902
|
+
riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(true, riskName));
|
|
16200
16903
|
}
|
|
16201
16904
|
}
|
|
16202
16905
|
return backtest$1.walkerCommandService.run(symbol, {
|
|
@@ -16232,8 +16935,12 @@ class WalkerInstance {
|
|
|
16232
16935
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(getErrorMessage(error))));
|
|
16233
16936
|
return () => {
|
|
16234
16937
|
for (const strategyName of walkerSchema.strategies) {
|
|
16235
|
-
backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
16236
|
-
walkerStopSubject.next({
|
|
16938
|
+
backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
|
|
16939
|
+
walkerStopSubject.next({
|
|
16940
|
+
symbol,
|
|
16941
|
+
strategyName,
|
|
16942
|
+
walkerName: context.walkerName,
|
|
16943
|
+
});
|
|
16237
16944
|
}
|
|
16238
16945
|
if (!this._isDone) {
|
|
16239
16946
|
doneWalkerSubject.next({
|
|
@@ -16278,7 +16985,7 @@ class WalkerInstance {
|
|
|
16278
16985
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16279
16986
|
for (const strategyName of walkerSchema.strategies) {
|
|
16280
16987
|
await walkerStopSubject.next({ symbol, strategyName, walkerName });
|
|
16281
|
-
await backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
16988
|
+
await backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
|
|
16282
16989
|
}
|
|
16283
16990
|
};
|
|
16284
16991
|
/**
|
|
@@ -16311,6 +17018,8 @@ class WalkerInstance {
|
|
|
16311
17018
|
*
|
|
16312
17019
|
* @param symbol - Trading symbol
|
|
16313
17020
|
* @param walkerName - Walker name to generate report for
|
|
17021
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17022
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16314
17023
|
* @returns Promise resolving to markdown formatted report string
|
|
16315
17024
|
*
|
|
16316
17025
|
* @example
|
|
@@ -16320,7 +17029,7 @@ class WalkerInstance {
|
|
|
16320
17029
|
* console.log(markdown);
|
|
16321
17030
|
* ```
|
|
16322
17031
|
*/
|
|
16323
|
-
this.getReport = async (symbol, walkerName) => {
|
|
17032
|
+
this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
|
|
16324
17033
|
backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_REPORT, {
|
|
16325
17034
|
symbol,
|
|
16326
17035
|
walkerName,
|
|
@@ -16329,7 +17038,7 @@ class WalkerInstance {
|
|
|
16329
17038
|
return await backtest$1.walkerMarkdownService.getReport(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
|
|
16330
17039
|
exchangeName: walkerSchema.exchangeName,
|
|
16331
17040
|
frameName: walkerSchema.frameName,
|
|
16332
|
-
});
|
|
17041
|
+
}, strategyColumns, pnlColumns);
|
|
16333
17042
|
};
|
|
16334
17043
|
/**
|
|
16335
17044
|
* Saves walker report to disk.
|
|
@@ -16337,6 +17046,8 @@ class WalkerInstance {
|
|
|
16337
17046
|
* @param symbol - Trading symbol
|
|
16338
17047
|
* @param walkerName - Walker name to save report for
|
|
16339
17048
|
* @param path - Optional directory path to save report (default: "./dump/walker")
|
|
17049
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17050
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16340
17051
|
*
|
|
16341
17052
|
* @example
|
|
16342
17053
|
* ```typescript
|
|
@@ -16348,7 +17059,7 @@ class WalkerInstance {
|
|
|
16348
17059
|
* await instance.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
16349
17060
|
* ```
|
|
16350
17061
|
*/
|
|
16351
|
-
this.dump = async (symbol, walkerName, path) => {
|
|
17062
|
+
this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
|
|
16352
17063
|
backtest$1.loggerService.info(WALKER_METHOD_NAME_DUMP, {
|
|
16353
17064
|
symbol,
|
|
16354
17065
|
walkerName,
|
|
@@ -16358,7 +17069,7 @@ class WalkerInstance {
|
|
|
16358
17069
|
await backtest$1.walkerMarkdownService.dump(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
|
|
16359
17070
|
exchangeName: walkerSchema.exchangeName,
|
|
16360
17071
|
frameName: walkerSchema.frameName,
|
|
16361
|
-
}, path);
|
|
17072
|
+
}, path, strategyColumns, pnlColumns);
|
|
16362
17073
|
};
|
|
16363
17074
|
}
|
|
16364
17075
|
}
|
|
@@ -16403,8 +17114,10 @@ class WalkerUtils {
|
|
|
16403
17114
|
for (const strategyName of walkerSchema.strategies) {
|
|
16404
17115
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_RUN);
|
|
16405
17116
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16406
|
-
riskName &&
|
|
16407
|
-
|
|
17117
|
+
riskName &&
|
|
17118
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN);
|
|
17119
|
+
riskList &&
|
|
17120
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN));
|
|
16408
17121
|
}
|
|
16409
17122
|
const instance = this._getInstance(symbol, context.walkerName);
|
|
16410
17123
|
return instance.run(symbol, context);
|
|
@@ -16436,8 +17149,10 @@ class WalkerUtils {
|
|
|
16436
17149
|
for (const strategyName of walkerSchema.strategies) {
|
|
16437
17150
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_BACKGROUND);
|
|
16438
17151
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16439
|
-
riskName &&
|
|
16440
|
-
|
|
17152
|
+
riskName &&
|
|
17153
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND);
|
|
17154
|
+
riskList &&
|
|
17155
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND));
|
|
16441
17156
|
}
|
|
16442
17157
|
const instance = this._getInstance(symbol, context.walkerName);
|
|
16443
17158
|
return instance.background(symbol, context);
|
|
@@ -16471,8 +17186,10 @@ class WalkerUtils {
|
|
|
16471
17186
|
for (const strategyName of walkerSchema.strategies) {
|
|
16472
17187
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_STOP);
|
|
16473
17188
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16474
|
-
riskName &&
|
|
16475
|
-
|
|
17189
|
+
riskName &&
|
|
17190
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP);
|
|
17191
|
+
riskList &&
|
|
17192
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP));
|
|
16476
17193
|
}
|
|
16477
17194
|
const instance = this._getInstance(symbol, walkerName);
|
|
16478
17195
|
return await instance.stop(symbol, walkerName);
|
|
@@ -16496,8 +17213,10 @@ class WalkerUtils {
|
|
|
16496
17213
|
for (const strategyName of walkerSchema.strategies) {
|
|
16497
17214
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_GET_DATA);
|
|
16498
17215
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16499
|
-
riskName &&
|
|
16500
|
-
|
|
17216
|
+
riskName &&
|
|
17217
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA);
|
|
17218
|
+
riskList &&
|
|
17219
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA));
|
|
16501
17220
|
}
|
|
16502
17221
|
const instance = this._getInstance(symbol, walkerName);
|
|
16503
17222
|
return await instance.getData(symbol, walkerName);
|
|
@@ -16507,6 +17226,8 @@ class WalkerUtils {
|
|
|
16507
17226
|
*
|
|
16508
17227
|
* @param symbol - Trading symbol
|
|
16509
17228
|
* @param walkerName - Walker name to generate report for
|
|
17229
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17230
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16510
17231
|
* @returns Promise resolving to markdown formatted report string
|
|
16511
17232
|
*
|
|
16512
17233
|
* @example
|
|
@@ -16515,17 +17236,19 @@ class WalkerUtils {
|
|
|
16515
17236
|
* console.log(markdown);
|
|
16516
17237
|
* ```
|
|
16517
17238
|
*/
|
|
16518
|
-
this.getReport = async (symbol, walkerName) => {
|
|
17239
|
+
this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
|
|
16519
17240
|
backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_GET_REPORT);
|
|
16520
17241
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16521
17242
|
for (const strategyName of walkerSchema.strategies) {
|
|
16522
17243
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_GET_REPORT);
|
|
16523
17244
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16524
|
-
riskName &&
|
|
16525
|
-
|
|
17245
|
+
riskName &&
|
|
17246
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT);
|
|
17247
|
+
riskList &&
|
|
17248
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT));
|
|
16526
17249
|
}
|
|
16527
17250
|
const instance = this._getInstance(symbol, walkerName);
|
|
16528
|
-
return await instance.getReport(symbol, walkerName);
|
|
17251
|
+
return await instance.getReport(symbol, walkerName, strategyColumns, pnlColumns);
|
|
16529
17252
|
};
|
|
16530
17253
|
/**
|
|
16531
17254
|
* Saves walker report to disk.
|
|
@@ -16533,6 +17256,8 @@ class WalkerUtils {
|
|
|
16533
17256
|
* @param symbol - Trading symbol
|
|
16534
17257
|
* @param walkerName - Walker name to save report for
|
|
16535
17258
|
* @param path - Optional directory path to save report (default: "./dump/walker")
|
|
17259
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17260
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16536
17261
|
*
|
|
16537
17262
|
* @example
|
|
16538
17263
|
* ```typescript
|
|
@@ -16543,17 +17268,19 @@ class WalkerUtils {
|
|
|
16543
17268
|
* await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
16544
17269
|
* ```
|
|
16545
17270
|
*/
|
|
16546
|
-
this.dump = async (symbol, walkerName, path) => {
|
|
17271
|
+
this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
|
|
16547
17272
|
backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_DUMP);
|
|
16548
17273
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16549
17274
|
for (const strategyName of walkerSchema.strategies) {
|
|
16550
17275
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_DUMP);
|
|
16551
17276
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16552
|
-
riskName &&
|
|
16553
|
-
|
|
17277
|
+
riskName &&
|
|
17278
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP);
|
|
17279
|
+
riskList &&
|
|
17280
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP));
|
|
16554
17281
|
}
|
|
16555
17282
|
const instance = this._getInstance(symbol, walkerName);
|
|
16556
|
-
return await instance.dump(symbol, walkerName, path);
|
|
17283
|
+
return await instance.dump(symbol, walkerName, path, strategyColumns, pnlColumns);
|
|
16557
17284
|
};
|
|
16558
17285
|
/**
|
|
16559
17286
|
* Lists all active walker instances with their current status.
|
|
@@ -16659,6 +17386,7 @@ class HeatUtils {
|
|
|
16659
17386
|
* Symbols are sorted by Total PNL descending.
|
|
16660
17387
|
*
|
|
16661
17388
|
* @param strategyName - Strategy name to generate heatmap report for
|
|
17389
|
+
* @param columns - Optional columns configuration for the report
|
|
16662
17390
|
* @returns Promise resolving to markdown formatted report string
|
|
16663
17391
|
*
|
|
16664
17392
|
* @example
|
|
@@ -16677,7 +17405,7 @@ class HeatUtils {
|
|
|
16677
17405
|
* // ...
|
|
16678
17406
|
* ```
|
|
16679
17407
|
*/
|
|
16680
|
-
this.getReport = async (strategyName) => {
|
|
17408
|
+
this.getReport = async (strategyName, columns) => {
|
|
16681
17409
|
backtest$1.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName });
|
|
16682
17410
|
backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_GET_REPORT);
|
|
16683
17411
|
{
|
|
@@ -16685,7 +17413,7 @@ class HeatUtils {
|
|
|
16685
17413
|
riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
|
|
16686
17414
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT));
|
|
16687
17415
|
}
|
|
16688
|
-
return await backtest$1.heatMarkdownService.getReport(strategyName);
|
|
17416
|
+
return await backtest$1.heatMarkdownService.getReport(strategyName, columns);
|
|
16689
17417
|
};
|
|
16690
17418
|
/**
|
|
16691
17419
|
* Saves heatmap report to disk for a strategy.
|
|
@@ -16695,6 +17423,7 @@ class HeatUtils {
|
|
|
16695
17423
|
*
|
|
16696
17424
|
* @param strategyName - Strategy name to save heatmap report for
|
|
16697
17425
|
* @param path - Optional directory path to save report (default: "./dump/heatmap")
|
|
17426
|
+
* @param columns - Optional columns configuration for the report
|
|
16698
17427
|
*
|
|
16699
17428
|
* @example
|
|
16700
17429
|
* ```typescript
|
|
@@ -16705,7 +17434,7 @@ class HeatUtils {
|
|
|
16705
17434
|
* await Heat.dump("my-strategy", "./reports");
|
|
16706
17435
|
* ```
|
|
16707
17436
|
*/
|
|
16708
|
-
this.dump = async (strategyName, path) => {
|
|
17437
|
+
this.dump = async (strategyName, path, columns) => {
|
|
16709
17438
|
backtest$1.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName, path });
|
|
16710
17439
|
backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_DUMP);
|
|
16711
17440
|
{
|
|
@@ -16713,7 +17442,7 @@ class HeatUtils {
|
|
|
16713
17442
|
riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
|
|
16714
17443
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP));
|
|
16715
17444
|
}
|
|
16716
|
-
await backtest$1.heatMarkdownService.dump(strategyName, path);
|
|
17445
|
+
await backtest$1.heatMarkdownService.dump(strategyName, path, columns);
|
|
16717
17446
|
};
|
|
16718
17447
|
}
|
|
16719
17448
|
}
|
|
@@ -17013,7 +17742,7 @@ class PartialUtils {
|
|
|
17013
17742
|
*
|
|
17014
17743
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17015
17744
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17016
|
-
* @returns Promise resolving to
|
|
17745
|
+
* @returns Promise resolving to PartialStatisticsModel object with counts and event list
|
|
17017
17746
|
*
|
|
17018
17747
|
* @example
|
|
17019
17748
|
* ```typescript
|
|
@@ -17057,6 +17786,7 @@ class PartialUtils {
|
|
|
17057
17786
|
*
|
|
17058
17787
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17059
17788
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17789
|
+
* @param columns - Optional columns configuration for the report
|
|
17060
17790
|
* @returns Promise resolving to markdown formatted report string
|
|
17061
17791
|
*
|
|
17062
17792
|
* @example
|
|
@@ -17077,7 +17807,7 @@ class PartialUtils {
|
|
|
17077
17807
|
* // **Loss events:** 1
|
|
17078
17808
|
* ```
|
|
17079
17809
|
*/
|
|
17080
|
-
this.getReport = async (symbol, strategyName) => {
|
|
17810
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
17081
17811
|
backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName });
|
|
17082
17812
|
backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
17083
17813
|
{
|
|
@@ -17085,7 +17815,7 @@ class PartialUtils {
|
|
|
17085
17815
|
riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
17086
17816
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT));
|
|
17087
17817
|
}
|
|
17088
|
-
return await backtest$1.partialMarkdownService.getReport(symbol, strategyName);
|
|
17818
|
+
return await backtest$1.partialMarkdownService.getReport(symbol, strategyName, columns);
|
|
17089
17819
|
};
|
|
17090
17820
|
/**
|
|
17091
17821
|
* Generates and saves markdown report to file.
|
|
@@ -17102,6 +17832,7 @@ class PartialUtils {
|
|
|
17102
17832
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17103
17833
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17104
17834
|
* @param path - Output directory path (default: "./dump/partial")
|
|
17835
|
+
* @param columns - Optional columns configuration for the report
|
|
17105
17836
|
* @returns Promise that resolves when file is written
|
|
17106
17837
|
*
|
|
17107
17838
|
* @example
|
|
@@ -17118,7 +17849,7 @@ class PartialUtils {
|
|
|
17118
17849
|
* }
|
|
17119
17850
|
* ```
|
|
17120
17851
|
*/
|
|
17121
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
17852
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
17122
17853
|
backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName, path });
|
|
17123
17854
|
backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_DUMP);
|
|
17124
17855
|
{
|
|
@@ -17126,7 +17857,7 @@ class PartialUtils {
|
|
|
17126
17857
|
riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
|
|
17127
17858
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP));
|
|
17128
17859
|
}
|
|
17129
|
-
await backtest$1.partialMarkdownService.dump(symbol, strategyName, path);
|
|
17860
|
+
await backtest$1.partialMarkdownService.dump(symbol, strategyName, path, columns);
|
|
17130
17861
|
};
|
|
17131
17862
|
}
|
|
17132
17863
|
}
|
|
@@ -17227,4 +17958,4 @@ class ConstantUtils {
|
|
|
17227
17958
|
*/
|
|
17228
17959
|
const Constant = new ConstantUtils();
|
|
17229
17960
|
|
|
17230
|
-
export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Risk, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getConfig, getDate, getDefaultConfig, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenRisk, listenRiskOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
|
|
17961
|
+
export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Risk, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getColumns, getConfig, getDate, getDefaultColumns, getDefaultConfig, getMode, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenRisk, listenRiskOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setColumns, setConfig, setLogger };
|