backtest-kit 1.5.24 → 1.5.26

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.
Files changed (5) hide show
  1. package/README.md +51 -7
  2. package/build/index.cjs +1618 -959
  3. package/build/index.mjs +1617 -961
  4. package/package.json +2 -2
  5. package/types.d.ts +4914 -4338
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, str, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
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'),
@@ -1763,53 +2839,6 @@ var emitters = /*#__PURE__*/Object.freeze({
1763
2839
  walkerStopSubject: walkerStopSubject
1764
2840
  });
1765
2841
 
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
2842
  const INTERVAL_MINUTES$1 = {
1814
2843
  "1m": 1,
1815
2844
  "3m": 3,
@@ -3388,7 +4417,7 @@ class RiskUtils {
3388
4417
  *
3389
4418
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3390
4419
  * @param strategyName - Strategy name (e.g., "my-strategy")
3391
- * @returns Promise resolving to RiskStatistics object with counts and event list
4420
+ * @returns Promise resolving to RiskStatisticsModel object with counts and event list
3392
4421
  *
3393
4422
  * @example
3394
4423
  * ```typescript
@@ -3435,6 +4464,7 @@ class RiskUtils {
3435
4464
  *
3436
4465
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3437
4466
  * @param strategyName - Strategy name (e.g., "my-strategy")
4467
+ * @param columns - Optional columns configuration for the report
3438
4468
  * @returns Promise resolving to markdown formatted report string
3439
4469
  *
3440
4470
  * @example
@@ -3458,7 +4488,7 @@ class RiskUtils {
3458
4488
  * // - my-strategy: 1
3459
4489
  * ```
3460
4490
  */
3461
- this.getReport = async (symbol, strategyName) => {
4491
+ this.getReport = async (symbol, strategyName, columns) => {
3462
4492
  backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, {
3463
4493
  symbol,
3464
4494
  strategyName,
@@ -3470,7 +4500,7 @@ class RiskUtils {
3470
4500
  backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
3471
4501
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT));
3472
4502
  }
3473
- return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
4503
+ return await backtest$1.riskMarkdownService.getReport(symbol, strategyName, columns);
3474
4504
  };
3475
4505
  /**
3476
4506
  * Generates and saves markdown report to file.
@@ -3487,6 +4517,7 @@ class RiskUtils {
3487
4517
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3488
4518
  * @param strategyName - Strategy name (e.g., "my-strategy")
3489
4519
  * @param path - Output directory path (default: "./dump/risk")
4520
+ * @param columns - Optional columns configuration for the report
3490
4521
  * @returns Promise that resolves when file is written
3491
4522
  *
3492
4523
  * @example
@@ -3503,7 +4534,7 @@ class RiskUtils {
3503
4534
  * }
3504
4535
  * ```
3505
4536
  */
3506
- this.dump = async (symbol, strategyName, path) => {
4537
+ this.dump = async (symbol, strategyName, path, columns) => {
3507
4538
  backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, {
3508
4539
  symbol,
3509
4540
  strategyName,
@@ -3516,7 +4547,7 @@ class RiskUtils {
3516
4547
  backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
3517
4548
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP));
3518
4549
  }
3519
- await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
4550
+ await backtest$1.riskMarkdownService.dump(symbol, strategyName, path, columns);
3520
4551
  };
3521
4552
  }
3522
4553
  }
@@ -5033,6 +6064,19 @@ class StrategySchemaService {
5033
6064
  if (typeof strategySchema.strategyName !== "string") {
5034
6065
  throw new Error(`strategy schema validation failed: missing strategyName`);
5035
6066
  }
6067
+ if (strategySchema.riskName && typeof strategySchema.riskName !== "string") {
6068
+ throw new Error(`strategy schema validation failed: invalid riskName`);
6069
+ }
6070
+ if (strategySchema.riskList && !Array.isArray(strategySchema.riskList)) {
6071
+ throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} system=${strategySchema.riskList}`);
6072
+ }
6073
+ if (strategySchema.riskList &&
6074
+ strategySchema.riskList.length !== new Set(strategySchema.riskList).size) {
6075
+ throw new Error(`strategy schema validation failed: found duplicate riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
6076
+ }
6077
+ if (strategySchema.riskList?.some((value) => typeof value !== "string")) {
6078
+ throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
6079
+ }
5036
6080
  if (typeof strategySchema.interval !== "string") {
5037
6081
  throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
5038
6082
  }
@@ -6263,178 +7307,91 @@ class BacktestCommandService {
6263
7307
  * @returns Async generator yielding closed signals with PNL
6264
7308
  */
6265
7309
  this.run = (symbol, context) => {
6266
- this.loggerService.log(METHOD_NAME_RUN$1, {
6267
- symbol,
6268
- context,
6269
- });
6270
- {
6271
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
6272
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
6273
- this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
6274
- }
6275
- {
6276
- 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
- ];
7310
+ this.loggerService.log(METHOD_NAME_RUN$1, {
7311
+ symbol,
7312
+ context,
7313
+ });
7314
+ {
7315
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
7316
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
7317
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
7318
+ }
7319
+ {
7320
+ const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
7321
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
7322
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
7323
+ }
7324
+ return this.backtestLogicPublicService.run(symbol, context);
7325
+ };
7326
+ }
7327
+ }
7328
+
7329
+ const METHOD_NAME_RUN = "walkerCommandService run";
7330
+ /**
7331
+ * Global service providing access to walker functionality.
7332
+ *
7333
+ * Simple wrapper around WalkerLogicPublicService for dependency injection.
7334
+ * Used by public API exports.
7335
+ */
7336
+ class WalkerCommandService {
7337
+ constructor() {
7338
+ this.loggerService = inject(TYPES.loggerService);
7339
+ this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
7340
+ this.walkerSchemaService = inject(TYPES.walkerSchemaService);
7341
+ this.strategyValidationService = inject(TYPES.strategyValidationService);
7342
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
7343
+ this.frameValidationService = inject(TYPES.frameValidationService);
7344
+ this.walkerValidationService = inject(TYPES.walkerValidationService);
7345
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
7346
+ this.riskValidationService = inject(TYPES.riskValidationService);
7347
+ /**
7348
+ * Runs walker comparison for a symbol with context propagation.
7349
+ *
7350
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
7351
+ * @param context - Walker context with strategies and metric
7352
+ */
7353
+ this.run = (symbol, context) => {
7354
+ this.loggerService.log(METHOD_NAME_RUN, {
7355
+ symbol,
7356
+ context,
7357
+ });
7358
+ {
7359
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
7360
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
7361
+ this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
7362
+ }
7363
+ {
7364
+ const walkerSchema = this.walkerSchemaService.get(context.walkerName);
7365
+ for (const strategyName of walkerSchema.strategies) {
7366
+ const { riskName, riskList } = this.strategySchemaService.get(strategyName);
7367
+ this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
7368
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
7369
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
7370
+ }
7371
+ }
7372
+ return this.walkerLogicPublicService.run(symbol, context);
7373
+ };
7374
+ }
7375
+ }
7376
+
7377
+ /**
7378
+ * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
7379
+ *
7380
+ * @param value - Value to check
7381
+ * @returns true if value is unsafe, false otherwise
7382
+ */
7383
+ function isUnsafe$3(value) {
7384
+ if (typeof value !== "number") {
7385
+ return true;
7386
+ }
7387
+ if (isNaN(value)) {
7388
+ return true;
7389
+ }
7390
+ if (!isFinite(value)) {
7391
+ return true;
7392
+ }
7393
+ return false;
7394
+ }
6438
7395
  /** Maximum number of signals to store in backtest reports */
6439
7396
  const MAX_EVENTS$6 = 250;
6440
7397
  /**
@@ -6528,9 +7485,10 @@ let ReportStorage$5 = class ReportStorage {
6528
7485
  * Generates markdown report with all closed signals for a strategy (View).
6529
7486
  *
6530
7487
  * @param strategyName - Strategy name
7488
+ * @param columns - Column configuration for formatting the table
6531
7489
  * @returns Markdown formatted report with all signals
6532
7490
  */
6533
- async getReport(strategyName) {
7491
+ async getReport(strategyName, columns = COLUMN_CONFIG.backtest_columns) {
6534
7492
  const stats = await this.getData();
6535
7493
  if (stats.totalSignals === 0) {
6536
7494
  return [
@@ -6539,10 +7497,15 @@ let ReportStorage$5 = class ReportStorage {
6539
7497
  "No signals closed yet."
6540
7498
  ].join("\n");
6541
7499
  }
6542
- const visibleColumns = columns$6.filter((col) => col.isVisible());
7500
+ const visibleColumns = [];
7501
+ for (const col of columns) {
7502
+ if (await col.isVisible()) {
7503
+ visibleColumns.push(col);
7504
+ }
7505
+ }
6543
7506
  const header = visibleColumns.map((col) => col.label);
6544
7507
  const separator = visibleColumns.map(() => "---");
6545
- const rows = this._signalList.map((closedSignal) => visibleColumns.map((col) => col.format(closedSignal)));
7508
+ const rows = await Promise.all(this._signalList.map(async (closedSignal, index) => Promise.all(visibleColumns.map((col) => col.format(closedSignal, index)))));
6546
7509
  const tableData = [header, separator, ...rows];
6547
7510
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6548
7511
  return [
@@ -6567,9 +7530,10 @@ let ReportStorage$5 = class ReportStorage {
6567
7530
  *
6568
7531
  * @param strategyName - Strategy name
6569
7532
  * @param path - Directory path to save report (default: "./dump/backtest")
7533
+ * @param columns - Column configuration for formatting the table
6570
7534
  */
6571
- async dump(strategyName, path = "./dump/backtest") {
6572
- const markdown = await this.getReport(strategyName);
7535
+ async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
7536
+ const markdown = await this.getReport(strategyName, columns);
6573
7537
  try {
6574
7538
  const dir = join(process.cwd(), path);
6575
7539
  await mkdir(dir, { recursive: true });
@@ -6677,6 +7641,7 @@ class BacktestMarkdownService {
6677
7641
  *
6678
7642
  * @param symbol - Trading pair symbol
6679
7643
  * @param strategyName - Strategy name to generate report for
7644
+ * @param columns - Column configuration for formatting the table
6680
7645
  * @returns Markdown formatted report string with table of all closed signals
6681
7646
  *
6682
7647
  * @example
@@ -6686,13 +7651,13 @@ class BacktestMarkdownService {
6686
7651
  * console.log(markdown);
6687
7652
  * ```
6688
7653
  */
6689
- this.getReport = async (symbol, strategyName) => {
7654
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.backtest_columns) => {
6690
7655
  this.loggerService.log("backtestMarkdownService getReport", {
6691
7656
  symbol,
6692
7657
  strategyName,
6693
7658
  });
6694
7659
  const storage = this.getStorage(symbol, strategyName);
6695
- return storage.getReport(strategyName);
7660
+ return storage.getReport(strategyName, columns);
6696
7661
  };
6697
7662
  /**
6698
7663
  * Saves symbol-strategy report to disk.
@@ -6702,6 +7667,7 @@ class BacktestMarkdownService {
6702
7667
  * @param symbol - Trading pair symbol
6703
7668
  * @param strategyName - Strategy name to save report for
6704
7669
  * @param path - Directory path to save report (default: "./dump/backtest")
7670
+ * @param columns - Column configuration for formatting the table
6705
7671
  *
6706
7672
  * @example
6707
7673
  * ```typescript
@@ -6714,14 +7680,14 @@ class BacktestMarkdownService {
6714
7680
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
6715
7681
  * ```
6716
7682
  */
6717
- this.dump = async (symbol, strategyName, path = "./dump/backtest") => {
7683
+ this.dump = async (symbol, strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) => {
6718
7684
  this.loggerService.log("backtestMarkdownService dump", {
6719
7685
  symbol,
6720
7686
  strategyName,
6721
7687
  path,
6722
7688
  });
6723
7689
  const storage = this.getStorage(symbol, strategyName);
6724
- await storage.dump(strategyName, path);
7690
+ await storage.dump(strategyName, path, columns);
6725
7691
  };
6726
7692
  /**
6727
7693
  * Clears accumulated signal data from storage.
@@ -6789,104 +7755,6 @@ function isUnsafe$2(value) {
6789
7755
  }
6790
7756
  return false;
6791
7757
  }
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
7758
  /** Maximum number of events to store in live trading reports */
6891
7759
  const MAX_EVENTS$5 = 250;
6892
7760
  /**
@@ -7098,9 +7966,10 @@ let ReportStorage$4 = class ReportStorage {
7098
7966
  * Generates markdown report with all tick events for a strategy (View).
7099
7967
  *
7100
7968
  * @param strategyName - Strategy name
7969
+ * @param columns - Column configuration for formatting the table
7101
7970
  * @returns Markdown formatted report with all events
7102
7971
  */
7103
- async getReport(strategyName) {
7972
+ async getReport(strategyName, columns = COLUMN_CONFIG.live_columns) {
7104
7973
  const stats = await this.getData();
7105
7974
  if (stats.totalEvents === 0) {
7106
7975
  return [
@@ -7109,10 +7978,15 @@ let ReportStorage$4 = class ReportStorage {
7109
7978
  "No events recorded yet."
7110
7979
  ].join("\n");
7111
7980
  }
7112
- const visibleColumns = columns$5.filter((col) => col.isVisible());
7981
+ const visibleColumns = [];
7982
+ for (const col of columns) {
7983
+ if (await col.isVisible()) {
7984
+ visibleColumns.push(col);
7985
+ }
7986
+ }
7113
7987
  const header = visibleColumns.map((col) => col.label);
7114
7988
  const separator = visibleColumns.map(() => "---");
7115
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
7989
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
7116
7990
  const tableData = [header, separator, ...rows];
7117
7991
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
7118
7992
  return [
@@ -7137,9 +8011,10 @@ let ReportStorage$4 = class ReportStorage {
7137
8011
  *
7138
8012
  * @param strategyName - Strategy name
7139
8013
  * @param path - Directory path to save report (default: "./dump/live")
8014
+ * @param columns - Column configuration for formatting the table
7140
8015
  */
7141
- async dump(strategyName, path = "./dump/live") {
7142
- const markdown = await this.getReport(strategyName);
8016
+ async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
8017
+ const markdown = await this.getReport(strategyName, columns);
7143
8018
  try {
7144
8019
  const dir = join(process.cwd(), path);
7145
8020
  await mkdir(dir, { recursive: true });
@@ -7260,6 +8135,7 @@ class LiveMarkdownService {
7260
8135
  *
7261
8136
  * @param symbol - Trading pair symbol
7262
8137
  * @param strategyName - Strategy name to generate report for
8138
+ * @param columns - Column configuration for formatting the table
7263
8139
  * @returns Markdown formatted report string with table of all events
7264
8140
  *
7265
8141
  * @example
@@ -7269,13 +8145,13 @@ class LiveMarkdownService {
7269
8145
  * console.log(markdown);
7270
8146
  * ```
7271
8147
  */
7272
- this.getReport = async (symbol, strategyName) => {
8148
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.live_columns) => {
7273
8149
  this.loggerService.log("liveMarkdownService getReport", {
7274
8150
  symbol,
7275
8151
  strategyName,
7276
8152
  });
7277
8153
  const storage = this.getStorage(symbol, strategyName);
7278
- return storage.getReport(strategyName);
8154
+ return storage.getReport(strategyName, columns);
7279
8155
  };
7280
8156
  /**
7281
8157
  * Saves symbol-strategy report to disk.
@@ -7285,6 +8161,7 @@ class LiveMarkdownService {
7285
8161
  * @param symbol - Trading pair symbol
7286
8162
  * @param strategyName - Strategy name to save report for
7287
8163
  * @param path - Directory path to save report (default: "./dump/live")
8164
+ * @param columns - Column configuration for formatting the table
7288
8165
  *
7289
8166
  * @example
7290
8167
  * ```typescript
@@ -7297,14 +8174,14 @@ class LiveMarkdownService {
7297
8174
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
7298
8175
  * ```
7299
8176
  */
7300
- this.dump = async (symbol, strategyName, path = "./dump/live") => {
8177
+ this.dump = async (symbol, strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) => {
7301
8178
  this.loggerService.log("liveMarkdownService dump", {
7302
8179
  symbol,
7303
8180
  strategyName,
7304
8181
  path,
7305
8182
  });
7306
8183
  const storage = this.getStorage(symbol, strategyName);
7307
- await storage.dump(strategyName, path);
8184
+ await storage.dump(strategyName, path, columns);
7308
8185
  };
7309
8186
  /**
7310
8187
  * Clears accumulated event data from storage.
@@ -7351,77 +8228,9 @@ class LiveMarkdownService {
7351
8228
  this.loggerService.log("liveMarkdownService init");
7352
8229
  signalLiveEmitter.subscribe(this.tick);
7353
8230
  });
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
- ];
8231
+ }
8232
+ }
8233
+
7425
8234
  /** Maximum number of events to store in schedule reports */
7426
8235
  const MAX_EVENTS$4 = 250;
7427
8236
  /**
@@ -7566,9 +8375,10 @@ let ReportStorage$3 = class ReportStorage {
7566
8375
  * Generates markdown report with all scheduled events for a strategy (View).
7567
8376
  *
7568
8377
  * @param strategyName - Strategy name
8378
+ * @param columns - Column configuration for formatting the table
7569
8379
  * @returns Markdown formatted report with all events
7570
8380
  */
7571
- async getReport(strategyName) {
8381
+ async getReport(strategyName, columns = COLUMN_CONFIG.schedule_columns) {
7572
8382
  const stats = await this.getData();
7573
8383
  if (stats.totalEvents === 0) {
7574
8384
  return [
@@ -7577,10 +8387,15 @@ let ReportStorage$3 = class ReportStorage {
7577
8387
  "No scheduled signals recorded yet."
7578
8388
  ].join("\n");
7579
8389
  }
7580
- const visibleColumns = columns$4.filter((col) => col.isVisible());
8390
+ const visibleColumns = [];
8391
+ for (const col of columns) {
8392
+ if (await col.isVisible()) {
8393
+ visibleColumns.push(col);
8394
+ }
8395
+ }
7581
8396
  const header = visibleColumns.map((col) => col.label);
7582
8397
  const separator = visibleColumns.map(() => "---");
7583
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
8398
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
7584
8399
  const tableData = [header, separator, ...rows];
7585
8400
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7586
8401
  return [
@@ -7603,9 +8418,10 @@ let ReportStorage$3 = class ReportStorage {
7603
8418
  *
7604
8419
  * @param strategyName - Strategy name
7605
8420
  * @param path - Directory path to save report (default: "./dump/schedule")
8421
+ * @param columns - Column configuration for formatting the table
7606
8422
  */
7607
- async dump(strategyName, path = "./dump/schedule") {
7608
- const markdown = await this.getReport(strategyName);
8423
+ async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
8424
+ const markdown = await this.getReport(strategyName, columns);
7609
8425
  try {
7610
8426
  const dir = join(process.cwd(), path);
7611
8427
  await mkdir(dir, { recursive: true });
@@ -7711,6 +8527,7 @@ class ScheduleMarkdownService {
7711
8527
  *
7712
8528
  * @param symbol - Trading pair symbol
7713
8529
  * @param strategyName - Strategy name to generate report for
8530
+ * @param columns - Column configuration for formatting the table
7714
8531
  * @returns Markdown formatted report string with table of all events
7715
8532
  *
7716
8533
  * @example
@@ -7720,13 +8537,13 @@ class ScheduleMarkdownService {
7720
8537
  * console.log(markdown);
7721
8538
  * ```
7722
8539
  */
7723
- this.getReport = async (symbol, strategyName) => {
8540
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.schedule_columns) => {
7724
8541
  this.loggerService.log("scheduleMarkdownService getReport", {
7725
8542
  symbol,
7726
8543
  strategyName,
7727
8544
  });
7728
8545
  const storage = this.getStorage(symbol, strategyName);
7729
- return storage.getReport(strategyName);
8546
+ return storage.getReport(strategyName, columns);
7730
8547
  };
7731
8548
  /**
7732
8549
  * Saves symbol-strategy report to disk.
@@ -7736,6 +8553,7 @@ class ScheduleMarkdownService {
7736
8553
  * @param symbol - Trading pair symbol
7737
8554
  * @param strategyName - Strategy name to save report for
7738
8555
  * @param path - Directory path to save report (default: "./dump/schedule")
8556
+ * @param columns - Column configuration for formatting the table
7739
8557
  *
7740
8558
  * @example
7741
8559
  * ```typescript
@@ -7748,14 +8566,14 @@ class ScheduleMarkdownService {
7748
8566
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
7749
8567
  * ```
7750
8568
  */
7751
- this.dump = async (symbol, strategyName, path = "./dump/schedule") => {
8569
+ this.dump = async (symbol, strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) => {
7752
8570
  this.loggerService.log("scheduleMarkdownService dump", {
7753
8571
  symbol,
7754
8572
  strategyName,
7755
8573
  path,
7756
8574
  });
7757
8575
  const storage = this.getStorage(symbol, strategyName);
7758
- await storage.dump(strategyName, path);
8576
+ await storage.dump(strategyName, path, columns);
7759
8577
  };
7760
8578
  /**
7761
8579
  * Clears accumulated event data from storage.
@@ -7814,86 +8632,6 @@ function percentile(sortedArray, p) {
7814
8632
  const index = Math.ceil((sortedArray.length * p) / 100) - 1;
7815
8633
  return sortedArray[Math.max(0, index)];
7816
8634
  }
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
8635
  /** Maximum number of performance events to store per strategy */
7898
8636
  const MAX_EVENTS$3 = 10000;
7899
8637
  /**
@@ -7996,9 +8734,10 @@ class PerformanceStorage {
7996
8734
  * Generates markdown report with performance statistics.
7997
8735
  *
7998
8736
  * @param strategyName - Strategy name
8737
+ * @param columns - Column configuration for formatting the table
7999
8738
  * @returns Markdown formatted report
8000
8739
  */
8001
- async getReport(strategyName) {
8740
+ async getReport(strategyName, columns = COLUMN_CONFIG.performance_columns) {
8002
8741
  const stats = await this.getData(strategyName);
8003
8742
  if (stats.totalEvents === 0) {
8004
8743
  return [
@@ -8010,10 +8749,15 @@ class PerformanceStorage {
8010
8749
  // Sort metrics by total duration (descending) to show bottlenecks first
8011
8750
  const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
8012
8751
  // Generate summary table using Column interface
8013
- const visibleColumns = columns$3.filter((col) => col.isVisible());
8752
+ const visibleColumns = [];
8753
+ for (const col of columns) {
8754
+ if (await col.isVisible()) {
8755
+ visibleColumns.push(col);
8756
+ }
8757
+ }
8014
8758
  const header = visibleColumns.map((col) => col.label);
8015
8759
  const separator = visibleColumns.map(() => "---");
8016
- const rows = sortedMetrics.map((metric) => visibleColumns.map((col) => col.format(metric)));
8760
+ const rows = await Promise.all(sortedMetrics.map(async (metric, index) => Promise.all(visibleColumns.map((col) => col.format(metric, index)))));
8017
8761
  const tableData = [header, separator, ...rows];
8018
8762
  const summaryTable = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8019
8763
  // Calculate percentage of total time for each metric
@@ -8044,9 +8788,10 @@ class PerformanceStorage {
8044
8788
  *
8045
8789
  * @param strategyName - Strategy name
8046
8790
  * @param path - Directory path to save report
8791
+ * @param columns - Column configuration for formatting the table
8047
8792
  */
8048
- async dump(strategyName, path = "./dump/performance") {
8049
- const markdown = await this.getReport(strategyName);
8793
+ async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
8794
+ const markdown = await this.getReport(strategyName, columns);
8050
8795
  try {
8051
8796
  const dir = join(process.cwd(), path);
8052
8797
  await mkdir(dir, { recursive: true });
@@ -8139,6 +8884,7 @@ class PerformanceMarkdownService {
8139
8884
  *
8140
8885
  * @param symbol - Trading pair symbol
8141
8886
  * @param strategyName - Strategy name to generate report for
8887
+ * @param columns - Column configuration for formatting the table
8142
8888
  * @returns Markdown formatted report string
8143
8889
  *
8144
8890
  * @example
@@ -8147,13 +8893,13 @@ class PerformanceMarkdownService {
8147
8893
  * console.log(markdown);
8148
8894
  * ```
8149
8895
  */
8150
- this.getReport = async (symbol, strategyName) => {
8896
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.performance_columns) => {
8151
8897
  this.loggerService.log("performanceMarkdownService getReport", {
8152
8898
  symbol,
8153
8899
  strategyName,
8154
8900
  });
8155
8901
  const storage = this.getStorage(symbol, strategyName);
8156
- return storage.getReport(strategyName);
8902
+ return storage.getReport(strategyName, columns);
8157
8903
  };
8158
8904
  /**
8159
8905
  * Saves performance report to disk.
@@ -8161,6 +8907,7 @@ class PerformanceMarkdownService {
8161
8907
  * @param symbol - Trading pair symbol
8162
8908
  * @param strategyName - Strategy name to save report for
8163
8909
  * @param path - Directory path to save report
8910
+ * @param columns - Column configuration for formatting the table
8164
8911
  *
8165
8912
  * @example
8166
8913
  * ```typescript
@@ -8171,14 +8918,14 @@ class PerformanceMarkdownService {
8171
8918
  * await performanceService.dump("BTCUSDT", "my-strategy", "./custom/path");
8172
8919
  * ```
8173
8920
  */
8174
- this.dump = async (symbol, strategyName, path = "./dump/performance") => {
8921
+ this.dump = async (symbol, strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) => {
8175
8922
  this.loggerService.log("performanceMarkdownService dump", {
8176
8923
  symbol,
8177
8924
  strategyName,
8178
8925
  path,
8179
8926
  });
8180
8927
  const storage = this.getStorage(symbol, strategyName);
8181
- await storage.dump(strategyName, path);
8928
+ await storage.dump(strategyName, path, columns);
8182
8929
  };
8183
8930
  /**
8184
8931
  * Clears accumulated performance data from storage.
@@ -8237,135 +8984,6 @@ function formatMetric(value) {
8237
8984
  }
8238
8985
  return value.toFixed(2);
8239
8986
  }
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
8987
  /**
8370
8988
  * Storage class for accumulating walker results.
8371
8989
  * Maintains a list of all strategy results and provides methods to generate reports.
@@ -8397,7 +9015,7 @@ let ReportStorage$2 = class ReportStorage {
8397
9015
  this._strategyResults.unshift({
8398
9016
  strategyName: data.strategyName,
8399
9017
  stats: data.stats,
8400
- metricValue: data.metricValue,
9018
+ metricValue: isUnsafe$1(data.metricValue) ? null : data.metricValue,
8401
9019
  });
8402
9020
  }
8403
9021
  /**
@@ -8430,11 +9048,11 @@ let ReportStorage$2 = class ReportStorage {
8430
9048
  * Generates comparison table for top N strategies (View).
8431
9049
  * Sorts strategies by metric value and formats as markdown table.
8432
9050
  *
8433
- * @param metric - Metric being optimized
8434
9051
  * @param topN - Number of top strategies to include (default: 10)
9052
+ * @param columns - Column configuration for formatting the strategy comparison table
8435
9053
  * @returns Markdown formatted comparison table
8436
9054
  */
8437
- getComparisonTable(metric, topN = 10) {
9055
+ async getComparisonTable(topN = 10, columns = COLUMN_CONFIG.walker_strategy_columns) {
8438
9056
  if (this._strategyResults.length === 0) {
8439
9057
  return "No strategy results available.";
8440
9058
  }
@@ -8447,13 +9065,17 @@ let ReportStorage$2 = class ReportStorage {
8447
9065
  // Take top N strategies
8448
9066
  const topStrategies = sortedResults.slice(0, topN);
8449
9067
  // Get columns configuration
8450
- const columns = createStrategyColumns(metric);
8451
- const visibleColumns = columns.filter((col) => col.isVisible());
9068
+ const visibleColumns = [];
9069
+ for (const col of columns) {
9070
+ if (await col.isVisible()) {
9071
+ visibleColumns.push(col);
9072
+ }
9073
+ }
8452
9074
  // Build table header
8453
9075
  const header = visibleColumns.map((col) => col.label);
8454
9076
  const separator = visibleColumns.map(() => "---");
8455
9077
  // Build table rows
8456
- const rows = topStrategies.map((result, index) => visibleColumns.map((col) => col.format(result, index)));
9078
+ const rows = await Promise.all(topStrategies.map(async (result, index) => Promise.all(visibleColumns.map((col) => col.format(result, index)))));
8457
9079
  const tableData = [header, separator, ...rows];
8458
9080
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8459
9081
  }
@@ -8461,9 +9083,10 @@ let ReportStorage$2 = class ReportStorage {
8461
9083
  * Generates PNL table showing all closed signals across all strategies (View).
8462
9084
  * Collects all signals from all strategies and formats as markdown table.
8463
9085
  *
9086
+ * @param columns - Column configuration for formatting the PNL table
8464
9087
  * @returns Markdown formatted PNL table
8465
9088
  */
8466
- getPnlTable() {
9089
+ async getPnlTable(columns = COLUMN_CONFIG.walker_pnl_columns) {
8467
9090
  if (this._strategyResults.length === 0) {
8468
9091
  return "No strategy results available.";
8469
9092
  }
@@ -8487,11 +9110,16 @@ let ReportStorage$2 = class ReportStorage {
8487
9110
  return "No closed signals available.";
8488
9111
  }
8489
9112
  // Build table header
8490
- const visibleColumns = pnlColumns.filter((col) => col.isVisible());
9113
+ const visibleColumns = [];
9114
+ for (const col of columns) {
9115
+ if (await col.isVisible()) {
9116
+ visibleColumns.push(col);
9117
+ }
9118
+ }
8491
9119
  const header = visibleColumns.map((col) => col.label);
8492
9120
  const separator = visibleColumns.map(() => "---");
8493
9121
  // Build table rows
8494
- const rows = allSignals.map((signal) => visibleColumns.map((col) => col.format(signal)));
9122
+ const rows = await Promise.all(allSignals.map(async (signal, index) => Promise.all(visibleColumns.map((col) => col.format(signal, index)))));
8495
9123
  const tableData = [header, separator, ...rows];
8496
9124
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8497
9125
  }
@@ -8502,9 +9130,11 @@ let ReportStorage$2 = class ReportStorage {
8502
9130
  * @param symbol - Trading symbol
8503
9131
  * @param metric - Metric being optimized
8504
9132
  * @param context - Context with exchangeName and frameName
9133
+ * @param strategyColumns - Column configuration for strategy comparison table
9134
+ * @param pnlColumns - Column configuration for PNL table
8505
9135
  * @returns Markdown formatted report with all results
8506
9136
  */
8507
- async getReport(symbol, metric, context) {
9137
+ async getReport(symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
8508
9138
  const results = await this.getData(symbol, metric, context);
8509
9139
  // Get total signals for best strategy
8510
9140
  const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
@@ -8524,11 +9154,11 @@ let ReportStorage$2 = class ReportStorage {
8524
9154
  "",
8525
9155
  "## Top Strategies Comparison",
8526
9156
  "",
8527
- this.getComparisonTable(metric, 10),
9157
+ await this.getComparisonTable(10, strategyColumns),
8528
9158
  "",
8529
9159
  "## All Signals (PNL Table)",
8530
9160
  "",
8531
- this.getPnlTable(),
9161
+ await this.getPnlTable(pnlColumns),
8532
9162
  "",
8533
9163
  "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
8534
9164
  ].join("\n");
@@ -8540,9 +9170,11 @@ let ReportStorage$2 = class ReportStorage {
8540
9170
  * @param metric - Metric being optimized
8541
9171
  * @param context - Context with exchangeName and frameName
8542
9172
  * @param path - Directory path to save report (default: "./dump/walker")
9173
+ * @param strategyColumns - Column configuration for strategy comparison table
9174
+ * @param pnlColumns - Column configuration for PNL table
8543
9175
  */
8544
- async dump(symbol, metric, context, path = "./dump/walker") {
8545
- const markdown = await this.getReport(symbol, metric, context);
9176
+ async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
9177
+ const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
8546
9178
  try {
8547
9179
  const dir = join(process.cwd(), path);
8548
9180
  await mkdir(dir, { recursive: true });
@@ -8635,6 +9267,8 @@ class WalkerMarkdownService {
8635
9267
  * @param symbol - Trading symbol
8636
9268
  * @param metric - Metric being optimized
8637
9269
  * @param context - Context with exchangeName and frameName
9270
+ * @param strategyColumns - Column configuration for strategy comparison table
9271
+ * @param pnlColumns - Column configuration for PNL table
8638
9272
  * @returns Markdown formatted report string
8639
9273
  *
8640
9274
  * @example
@@ -8644,7 +9278,7 @@ class WalkerMarkdownService {
8644
9278
  * console.log(markdown);
8645
9279
  * ```
8646
9280
  */
8647
- this.getReport = async (walkerName, symbol, metric, context) => {
9281
+ this.getReport = async (walkerName, symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
8648
9282
  this.loggerService.log("walkerMarkdownService getReport", {
8649
9283
  walkerName,
8650
9284
  symbol,
@@ -8652,7 +9286,7 @@ class WalkerMarkdownService {
8652
9286
  context,
8653
9287
  });
8654
9288
  const storage = this.getStorage(walkerName);
8655
- return storage.getReport(symbol, metric, context);
9289
+ return storage.getReport(symbol, metric, context, strategyColumns, pnlColumns);
8656
9290
  };
8657
9291
  /**
8658
9292
  * Saves walker report to disk.
@@ -8664,6 +9298,8 @@ class WalkerMarkdownService {
8664
9298
  * @param metric - Metric being optimized
8665
9299
  * @param context - Context with exchangeName and frameName
8666
9300
  * @param path - Directory path to save report (default: "./dump/walker")
9301
+ * @param strategyColumns - Column configuration for strategy comparison table
9302
+ * @param pnlColumns - Column configuration for PNL table
8667
9303
  *
8668
9304
  * @example
8669
9305
  * ```typescript
@@ -8676,7 +9312,7 @@ class WalkerMarkdownService {
8676
9312
  * await service.dump("my-walker", "BTCUSDT", "sharpeRatio", { exchangeName: "binance", frameName: "1d" }, "./custom/path");
8677
9313
  * ```
8678
9314
  */
8679
- this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker") => {
9315
+ this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
8680
9316
  this.loggerService.log("walkerMarkdownService dump", {
8681
9317
  walkerName,
8682
9318
  symbol,
@@ -8685,7 +9321,7 @@ class WalkerMarkdownService {
8685
9321
  path,
8686
9322
  });
8687
9323
  const storage = this.getStorage(walkerName);
8688
- await storage.dump(symbol, metric, context, path);
9324
+ await storage.dump(symbol, metric, context, path, strategyColumns, pnlColumns);
8689
9325
  };
8690
9326
  /**
8691
9327
  * Clears accumulated result data from storage.
@@ -8732,99 +9368,25 @@ class WalkerMarkdownService {
8732
9368
  const HEATMAP_METHOD_NAME_GET_DATA = "HeatMarkdownService.getData";
8733
9369
  const HEATMAP_METHOD_NAME_GET_REPORT = "HeatMarkdownService.getReport";
8734
9370
  const HEATMAP_METHOD_NAME_DUMP = "HeatMarkdownService.dump";
8735
- const HEATMAP_METHOD_NAME_CLEAR = "HeatMarkdownService.clear";
8736
- /**
8737
- * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
8738
- *
8739
- * @param value - Value to check
8740
- * @returns true if value is unsafe, false otherwise
8741
- */
8742
- function isUnsafe(value) {
8743
- if (typeof value !== "number") {
8744
- return true;
8745
- }
8746
- if (isNaN(value)) {
8747
- return true;
8748
- }
8749
- if (!isFinite(value)) {
8750
- return true;
8751
- }
8752
- return false;
8753
- }
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
- ];
9371
+ const HEATMAP_METHOD_NAME_CLEAR = "HeatMarkdownService.clear";
9372
+ /**
9373
+ * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
9374
+ *
9375
+ * @param value - Value to check
9376
+ * @returns true if value is unsafe, false otherwise
9377
+ */
9378
+ function isUnsafe(value) {
9379
+ if (typeof value !== "number") {
9380
+ return true;
9381
+ }
9382
+ if (isNaN(value)) {
9383
+ return true;
9384
+ }
9385
+ if (!isFinite(value)) {
9386
+ return true;
9387
+ }
9388
+ return false;
9389
+ }
8828
9390
  /** Maximum number of signals to store per symbol in heatmap reports */
8829
9391
  const MAX_EVENTS$2 = 250;
8830
9392
  /**
@@ -9059,9 +9621,10 @@ class HeatmapStorage {
9059
9621
  * Generates markdown report with portfolio heatmap table (View).
9060
9622
  *
9061
9623
  * @param strategyName - Strategy name for report title
9624
+ * @param columns - Column configuration for formatting the table
9062
9625
  * @returns Promise resolving to markdown formatted report string
9063
9626
  */
9064
- async getReport(strategyName) {
9627
+ async getReport(strategyName, columns = COLUMN_CONFIG.heat_columns) {
9065
9628
  const data = await this.getData();
9066
9629
  if (data.symbols.length === 0) {
9067
9630
  return [
@@ -9070,10 +9633,15 @@ class HeatmapStorage {
9070
9633
  "*No data available*"
9071
9634
  ].join("\n");
9072
9635
  }
9073
- const visibleColumns = columns$2.filter((col) => col.isVisible());
9636
+ const visibleColumns = [];
9637
+ for (const col of columns) {
9638
+ if (await col.isVisible()) {
9639
+ visibleColumns.push(col);
9640
+ }
9641
+ }
9074
9642
  const header = visibleColumns.map((col) => col.label);
9075
9643
  const separator = visibleColumns.map(() => "---");
9076
- const rows = data.symbols.map((row) => visibleColumns.map((col) => col.format(row)));
9644
+ const rows = await Promise.all(data.symbols.map(async (row, index) => Promise.all(visibleColumns.map((col) => col.format(row, index)))));
9077
9645
  const tableData = [header, separator, ...rows];
9078
9646
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
9079
9647
  return [
@@ -9089,9 +9657,10 @@ class HeatmapStorage {
9089
9657
  *
9090
9658
  * @param strategyName - Strategy name for filename
9091
9659
  * @param path - Directory path to save report (default: "./dump/heatmap")
9660
+ * @param columns - Column configuration for formatting the table
9092
9661
  */
9093
- async dump(strategyName, path = "./dump/heatmap") {
9094
- const markdown = await this.getReport(strategyName);
9662
+ async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
9663
+ const markdown = await this.getReport(strategyName, columns);
9095
9664
  try {
9096
9665
  const dir = join(process.cwd(), path);
9097
9666
  await mkdir(dir, { recursive: true });
@@ -9188,6 +9757,7 @@ class HeatMarkdownService {
9188
9757
  * Generates markdown report with portfolio heatmap table for a strategy.
9189
9758
  *
9190
9759
  * @param strategyName - Strategy name to generate heatmap report for
9760
+ * @param columns - Column configuration for formatting the table
9191
9761
  * @returns Promise resolving to markdown formatted report string
9192
9762
  *
9193
9763
  * @example
@@ -9207,12 +9777,12 @@ class HeatMarkdownService {
9207
9777
  * // ...
9208
9778
  * ```
9209
9779
  */
9210
- this.getReport = async (strategyName) => {
9780
+ this.getReport = async (strategyName, columns = COLUMN_CONFIG.heat_columns) => {
9211
9781
  this.loggerService.log(HEATMAP_METHOD_NAME_GET_REPORT, {
9212
9782
  strategyName,
9213
9783
  });
9214
9784
  const storage = this.getStorage(strategyName);
9215
- return storage.getReport(strategyName);
9785
+ return storage.getReport(strategyName, columns);
9216
9786
  };
9217
9787
  /**
9218
9788
  * Saves heatmap report to disk for a strategy.
@@ -9222,6 +9792,7 @@ class HeatMarkdownService {
9222
9792
  *
9223
9793
  * @param strategyName - Strategy name to save heatmap report for
9224
9794
  * @param path - Optional directory path to save report (default: "./dump/heatmap")
9795
+ * @param columns - Column configuration for formatting the table
9225
9796
  *
9226
9797
  * @example
9227
9798
  * ```typescript
@@ -9234,13 +9805,13 @@ class HeatMarkdownService {
9234
9805
  * await service.dump("my-strategy", "./reports");
9235
9806
  * ```
9236
9807
  */
9237
- this.dump = async (strategyName, path = "./dump/heatmap") => {
9808
+ this.dump = async (strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) => {
9238
9809
  this.loggerService.log(HEATMAP_METHOD_NAME_DUMP, {
9239
9810
  strategyName,
9240
9811
  path,
9241
9812
  });
9242
9813
  const storage = this.getStorage(strategyName);
9243
- await storage.dump(strategyName, path);
9814
+ await storage.dump(strategyName, path, columns);
9244
9815
  };
9245
9816
  /**
9246
9817
  * Clears accumulated heatmap data from storage.
@@ -11631,62 +12202,6 @@ class PartialConnectionService {
11631
12202
  }
11632
12203
  }
11633
12204
 
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
12205
  /** Maximum number of events to store in partial reports */
11691
12206
  const MAX_EVENTS$1 = 250;
11692
12207
  /**
@@ -11776,9 +12291,10 @@ let ReportStorage$1 = class ReportStorage {
11776
12291
  *
11777
12292
  * @param symbol - Trading pair symbol
11778
12293
  * @param strategyName - Strategy name
12294
+ * @param columns - Column configuration for formatting the table
11779
12295
  * @returns Markdown formatted report with all events
11780
12296
  */
11781
- async getReport(symbol, strategyName) {
12297
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) {
11782
12298
  const stats = await this.getData();
11783
12299
  if (stats.totalEvents === 0) {
11784
12300
  return [
@@ -11787,10 +12303,15 @@ let ReportStorage$1 = class ReportStorage {
11787
12303
  "No partial profit/loss events recorded yet."
11788
12304
  ].join("\n");
11789
12305
  }
11790
- const visibleColumns = columns$1.filter((col) => col.isVisible());
12306
+ const visibleColumns = [];
12307
+ for (const col of columns) {
12308
+ if (await col.isVisible()) {
12309
+ visibleColumns.push(col);
12310
+ }
12311
+ }
11791
12312
  const header = visibleColumns.map((col) => col.label);
11792
12313
  const separator = visibleColumns.map(() => "---");
11793
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
12314
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
11794
12315
  const tableData = [header, separator, ...rows];
11795
12316
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
11796
12317
  return [
@@ -11809,9 +12330,10 @@ let ReportStorage$1 = class ReportStorage {
11809
12330
  * @param symbol - Trading pair symbol
11810
12331
  * @param strategyName - Strategy name
11811
12332
  * @param path - Directory path to save report (default: "./dump/partial")
12333
+ * @param columns - Column configuration for formatting the table
11812
12334
  */
11813
- async dump(symbol, strategyName, path = "./dump/partial") {
11814
- const markdown = await this.getReport(symbol, strategyName);
12335
+ async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
12336
+ const markdown = await this.getReport(symbol, strategyName, columns);
11815
12337
  try {
11816
12338
  const dir = join(process.cwd(), path);
11817
12339
  await mkdir(dir, { recursive: true });
@@ -11922,6 +12444,7 @@ class PartialMarkdownService {
11922
12444
  *
11923
12445
  * @param symbol - Trading pair symbol to generate report for
11924
12446
  * @param strategyName - Strategy name to generate report for
12447
+ * @param columns - Column configuration for formatting the table
11925
12448
  * @returns Markdown formatted report string with table of all events
11926
12449
  *
11927
12450
  * @example
@@ -11931,13 +12454,13 @@ class PartialMarkdownService {
11931
12454
  * console.log(markdown);
11932
12455
  * ```
11933
12456
  */
11934
- this.getReport = async (symbol, strategyName) => {
12457
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) => {
11935
12458
  this.loggerService.log("partialMarkdownService getReport", {
11936
12459
  symbol,
11937
12460
  strategyName,
11938
12461
  });
11939
12462
  const storage = this.getStorage(symbol, strategyName);
11940
- return storage.getReport(symbol, strategyName);
12463
+ return storage.getReport(symbol, strategyName, columns);
11941
12464
  };
11942
12465
  /**
11943
12466
  * Saves symbol-strategy report to disk.
@@ -11947,6 +12470,7 @@ class PartialMarkdownService {
11947
12470
  * @param symbol - Trading pair symbol to save report for
11948
12471
  * @param strategyName - Strategy name to save report for
11949
12472
  * @param path - Directory path to save report (default: "./dump/partial")
12473
+ * @param columns - Column configuration for formatting the table
11950
12474
  *
11951
12475
  * @example
11952
12476
  * ```typescript
@@ -11959,14 +12483,14 @@ class PartialMarkdownService {
11959
12483
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
11960
12484
  * ```
11961
12485
  */
11962
- this.dump = async (symbol, strategyName, path = "./dump/partial") => {
12486
+ this.dump = async (symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) => {
11963
12487
  this.loggerService.log("partialMarkdownService dump", {
11964
12488
  symbol,
11965
12489
  strategyName,
11966
12490
  path,
11967
12491
  });
11968
12492
  const storage = this.getStorage(symbol, strategyName);
11969
- await storage.dump(symbol, strategyName, path);
12493
+ await storage.dump(symbol, strategyName, path, columns);
11970
12494
  };
11971
12495
  /**
11972
12496
  * Clears accumulated event data from storage.
@@ -12426,92 +12950,6 @@ class ConfigValidationService {
12426
12950
  }
12427
12951
  }
12428
12952
 
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
12953
  /** Maximum number of events to store in risk reports */
12516
12954
  const MAX_EVENTS = 250;
12517
12955
  /**
@@ -12567,9 +13005,10 @@ class ReportStorage {
12567
13005
  *
12568
13006
  * @param symbol - Trading pair symbol
12569
13007
  * @param strategyName - Strategy name
13008
+ * @param columns - Column configuration for formatting the table
12570
13009
  * @returns Markdown formatted report with all events
12571
13010
  */
12572
- async getReport(symbol, strategyName) {
13011
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) {
12573
13012
  const stats = await this.getData();
12574
13013
  if (stats.totalRejections === 0) {
12575
13014
  return [
@@ -12578,10 +13017,15 @@ class ReportStorage {
12578
13017
  "No risk rejections recorded yet.",
12579
13018
  ].join("\n");
12580
13019
  }
12581
- const visibleColumns = columns.filter((col) => col.isVisible());
13020
+ const visibleColumns = [];
13021
+ for (const col of columns) {
13022
+ if (await col.isVisible()) {
13023
+ visibleColumns.push(col);
13024
+ }
13025
+ }
12582
13026
  const header = visibleColumns.map((col) => col.label);
12583
13027
  const separator = visibleColumns.map(() => "---");
12584
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
13028
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
12585
13029
  const tableData = [header, separator, ...rows];
12586
13030
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
12587
13031
  return [
@@ -12604,9 +13048,10 @@ class ReportStorage {
12604
13048
  * @param symbol - Trading pair symbol
12605
13049
  * @param strategyName - Strategy name
12606
13050
  * @param path - Directory path to save report (default: "./dump/risk")
13051
+ * @param columns - Column configuration for formatting the table
12607
13052
  */
12608
- async dump(symbol, strategyName, path = "./dump/risk") {
12609
- const markdown = await this.getReport(symbol, strategyName);
13053
+ async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
13054
+ const markdown = await this.getReport(symbol, strategyName, columns);
12610
13055
  try {
12611
13056
  const dir = join(process.cwd(), path);
12612
13057
  await mkdir(dir, { recursive: true });
@@ -12698,6 +13143,7 @@ class RiskMarkdownService {
12698
13143
  *
12699
13144
  * @param symbol - Trading pair symbol to generate report for
12700
13145
  * @param strategyName - Strategy name to generate report for
13146
+ * @param columns - Column configuration for formatting the table
12701
13147
  * @returns Markdown formatted report string with table of all events
12702
13148
  *
12703
13149
  * @example
@@ -12707,13 +13153,13 @@ class RiskMarkdownService {
12707
13153
  * console.log(markdown);
12708
13154
  * ```
12709
13155
  */
12710
- this.getReport = async (symbol, strategyName) => {
13156
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) => {
12711
13157
  this.loggerService.log("riskMarkdownService getReport", {
12712
13158
  symbol,
12713
13159
  strategyName,
12714
13160
  });
12715
13161
  const storage = this.getStorage(symbol, strategyName);
12716
- return storage.getReport(symbol, strategyName);
13162
+ return storage.getReport(symbol, strategyName, columns);
12717
13163
  };
12718
13164
  /**
12719
13165
  * Saves symbol-strategy report to disk.
@@ -12723,6 +13169,7 @@ class RiskMarkdownService {
12723
13169
  * @param symbol - Trading pair symbol to save report for
12724
13170
  * @param strategyName - Strategy name to save report for
12725
13171
  * @param path - Directory path to save report (default: "./dump/risk")
13172
+ * @param columns - Column configuration for formatting the table
12726
13173
  *
12727
13174
  * @example
12728
13175
  * ```typescript
@@ -12735,14 +13182,14 @@ class RiskMarkdownService {
12735
13182
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
12736
13183
  * ```
12737
13184
  */
12738
- this.dump = async (symbol, strategyName, path = "./dump/risk") => {
13185
+ this.dump = async (symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) => {
12739
13186
  this.loggerService.log("riskMarkdownService dump", {
12740
13187
  symbol,
12741
13188
  strategyName,
12742
13189
  path,
12743
13190
  });
12744
13191
  const storage = this.getStorage(symbol, strategyName);
12745
- await storage.dump(symbol, strategyName, path);
13192
+ await storage.dump(symbol, strategyName, path, columns);
12746
13193
  };
12747
13194
  /**
12748
13195
  * Clears accumulated event data from storage.
@@ -12792,6 +13239,118 @@ class RiskMarkdownService {
12792
13239
  }
12793
13240
  }
12794
13241
 
13242
+ /**
13243
+ * Service for validating column configurations to ensure consistency with ColumnModel interface
13244
+ * and prevent invalid column definitions.
13245
+ *
13246
+ * Performs comprehensive validation on all column definitions in COLUMN_CONFIG:
13247
+ * - **Required fields**: All columns must have key, label, format, and isVisible properties
13248
+ * - **Unique keys**: All key values must be unique within each column collection
13249
+ * - **Function validation**: format and isVisible must be callable functions
13250
+ * - **Data types**: key and label must be non-empty strings
13251
+ *
13252
+ * @throws {Error} If any validation fails, throws with detailed breakdown of all errors
13253
+ *
13254
+ * @example
13255
+ * ```typescript
13256
+ * const validator = new ColumnValidationService();
13257
+ * validator.validate(); // Throws if column configuration is invalid
13258
+ * ```
13259
+ *
13260
+ * @example Validation failure output:
13261
+ * ```
13262
+ * Column configuration validation failed:
13263
+ * 1. backtest_columns[0]: Missing required field "format"
13264
+ * 2. heat_columns: Duplicate key "symbol" at indexes 1, 5
13265
+ * 3. live_columns[3].isVisible must be a function, got "boolean"
13266
+ * ```
13267
+ */
13268
+ class ColumnValidationService {
13269
+ constructor() {
13270
+ /**
13271
+ * @private
13272
+ * @readonly
13273
+ * Injected logger service instance
13274
+ */
13275
+ this.loggerService = inject(TYPES.loggerService);
13276
+ /**
13277
+ * Validates all column configurations in COLUMN_CONFIG for structural correctness.
13278
+ *
13279
+ * Checks:
13280
+ * 1. All required fields (key, label, format, isVisible) are present in each column
13281
+ * 2. key and label are non-empty strings
13282
+ * 3. format and isVisible are functions (not other types)
13283
+ * 4. All keys are unique within each column collection
13284
+ *
13285
+ * @throws Error if configuration is invalid
13286
+ */
13287
+ this.validate = () => {
13288
+ this.loggerService.log("columnValidationService validate");
13289
+ const errors = [];
13290
+ // Iterate through all column collections in COLUMN_CONFIG
13291
+ for (const [configKey, columns] of Object.entries(COLUMN_CONFIG)) {
13292
+ if (!Array.isArray(columns)) {
13293
+ errors.push(`${configKey} is not an array, got ${typeof columns}`);
13294
+ continue;
13295
+ }
13296
+ // Track keys for uniqueness check
13297
+ const keyMap = new Map();
13298
+ // Validate each column in the collection
13299
+ columns.forEach((column, index) => {
13300
+ if (!column || typeof column !== "object") {
13301
+ errors.push(`${configKey}[${index}]: Column must be an object, got ${typeof column}`);
13302
+ return;
13303
+ }
13304
+ // Check for all required fields
13305
+ const requiredFields = ["key", "label", "format", "isVisible"];
13306
+ for (const field of requiredFields) {
13307
+ if (!(field in column)) {
13308
+ errors.push(`${configKey}[${index}]: Missing required field "${field}"`);
13309
+ }
13310
+ }
13311
+ // Validate key and label are non-empty strings
13312
+ if (typeof column.key !== "string" || column.key.trim() === "") {
13313
+ errors.push(`${configKey}[${index}].key must be a non-empty string, got ${typeof column.key === "string" ? `"${column.key}"` : typeof column.key}`);
13314
+ }
13315
+ else {
13316
+ // Track key for uniqueness check
13317
+ if (!keyMap.has(column.key)) {
13318
+ keyMap.set(column.key, []);
13319
+ }
13320
+ keyMap.get(column.key).push(index);
13321
+ }
13322
+ if (typeof column.label !== "string" || column.label.trim() === "") {
13323
+ errors.push(`${configKey}[${index}].label must be a non-empty string, got ${typeof column.label === "string" ? `"${column.label}"` : typeof column.label}`);
13324
+ }
13325
+ // Validate format is a function
13326
+ if (typeof column.format !== "function") {
13327
+ errors.push(`${configKey}[${index}].format must be a function, got "${typeof column.format}"`);
13328
+ }
13329
+ // Validate isVisible is a function
13330
+ if (typeof column.isVisible !== "function") {
13331
+ errors.push(`${configKey}[${index}].isVisible must be a function, got "${typeof column.isVisible}"`);
13332
+ }
13333
+ });
13334
+ // Check for duplicate keys
13335
+ for (const [key, indexes] of keyMap.entries()) {
13336
+ if (indexes.length > 1) {
13337
+ errors.push(`${configKey}: Duplicate key "${key}" at indexes ${indexes.join(", ")}`);
13338
+ }
13339
+ }
13340
+ }
13341
+ // Throw aggregated errors if any
13342
+ if (errors.length > 0) {
13343
+ const errorMessage = `Column configuration validation failed:\n${errors
13344
+ .map((e, i) => ` ${i + 1}. ${e}`)
13345
+ .join("\n")}`;
13346
+ this.loggerService.warn(errorMessage);
13347
+ throw new Error(errorMessage);
13348
+ }
13349
+ this.loggerService.log("columnValidationService validation passed");
13350
+ };
13351
+ }
13352
+ }
13353
+
12795
13354
  {
12796
13355
  provide(TYPES.loggerService, () => new LoggerService());
12797
13356
  }
@@ -12863,6 +13422,7 @@ class RiskMarkdownService {
12863
13422
  provide(TYPES.riskValidationService, () => new RiskValidationService());
12864
13423
  provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
12865
13424
  provide(TYPES.configValidationService, () => new ConfigValidationService());
13425
+ provide(TYPES.columnValidationService, () => new ColumnValidationService());
12866
13426
  }
12867
13427
  {
12868
13428
  provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
@@ -12939,6 +13499,7 @@ const validationServices = {
12939
13499
  riskValidationService: inject(TYPES.riskValidationService),
12940
13500
  optimizerValidationService: inject(TYPES.optimizerValidationService),
12941
13501
  configValidationService: inject(TYPES.configValidationService),
13502
+ columnValidationService: inject(TYPES.columnValidationService),
12942
13503
  };
12943
13504
  const templateServices = {
12944
13505
  optimizerTemplateService: inject(TYPES.optimizerTemplateService),
@@ -13038,6 +13599,77 @@ function getConfig() {
13038
13599
  function getDefaultConfig() {
13039
13600
  return DEFAULT_CONFIG;
13040
13601
  }
13602
+ /**
13603
+ * Sets custom column configurations for markdown report generation.
13604
+ *
13605
+ * Allows overriding default column definitions for any report type.
13606
+ * All columns are validated before assignment to ensure structural correctness.
13607
+ *
13608
+ * @param columns - Partial column configuration object to override default column settings
13609
+ * @param _unsafe - Skip column validations - required for testbed
13610
+ *
13611
+ * @example
13612
+ * ```typescript
13613
+ * setColumns({
13614
+ * backtest_columns: [
13615
+ * {
13616
+ * key: "customId",
13617
+ * label: "Custom ID",
13618
+ * format: (data) => data.signal.id,
13619
+ * isVisible: () => true
13620
+ * }
13621
+ * ],
13622
+ * });
13623
+ * ```
13624
+ *
13625
+ * @throws {Error} If column configuration is invalid
13626
+ */
13627
+ function setColumns(columns, _unsafe) {
13628
+ const prevConfig = Object.assign({}, COLUMN_CONFIG);
13629
+ try {
13630
+ Object.assign(COLUMN_CONFIG, columns);
13631
+ !_unsafe && backtest$1.columnValidationService.validate();
13632
+ }
13633
+ catch (error) {
13634
+ console.warn(`backtest-kit setColumns failed: ${getErrorMessage(error)}`, columns);
13635
+ Object.assign(COLUMN_CONFIG, prevConfig);
13636
+ throw error;
13637
+ }
13638
+ }
13639
+ /**
13640
+ * Retrieves a copy of the current column configuration for markdown report generation.
13641
+ *
13642
+ * Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
13643
+ * Use this to inspect the current column definitions without modifying them.
13644
+ *
13645
+ * @returns {ColumnConfig} A copy of the current column configuration object
13646
+ *
13647
+ * @example
13648
+ * ```typescript
13649
+ * const currentColumns = getColumns();
13650
+ * console.log(currentColumns.backtest_columns.length);
13651
+ * ```
13652
+ */
13653
+ function getColumns() {
13654
+ return Object.assign({}, COLUMN_CONFIG);
13655
+ }
13656
+ /**
13657
+ * Retrieves the default column configuration object for markdown report generation.
13658
+ *
13659
+ * Returns a reference to the default column definitions with all preset values.
13660
+ * Use this to see what column options are available and their default definitions.
13661
+ *
13662
+ * @returns {ColumnConfig} The default column configuration object
13663
+ *
13664
+ * @example
13665
+ * ```typescript
13666
+ * const defaultColumns = getDefaultColumns();
13667
+ * console.log(defaultColumns.backtest_columns);
13668
+ * ```
13669
+ */
13670
+ function getDefaultColumns() {
13671
+ return DEFAULT_COLUMNS;
13672
+ }
13041
13673
 
13042
13674
  const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
13043
13675
  const ADD_EXCHANGE_METHOD_NAME = "add.addExchange";
@@ -15023,6 +15655,7 @@ class BacktestInstance {
15023
15655
  *
15024
15656
  * @param symbol - Trading pair symbol
15025
15657
  * @param strategyName - Strategy name to generate report for
15658
+ * @param columns - Optional columns configuration for the report
15026
15659
  * @returns Promise resolving to markdown formatted report string
15027
15660
  *
15028
15661
  * @example
@@ -15032,12 +15665,12 @@ class BacktestInstance {
15032
15665
  * console.log(markdown);
15033
15666
  * ```
15034
15667
  */
15035
- this.getReport = async (symbol, strategyName) => {
15668
+ this.getReport = async (symbol, strategyName, columns) => {
15036
15669
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_REPORT, {
15037
15670
  symbol,
15038
15671
  strategyName,
15039
15672
  });
15040
- return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName);
15673
+ return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName, columns);
15041
15674
  };
15042
15675
  /**
15043
15676
  * Saves strategy report to disk.
@@ -15045,6 +15678,7 @@ class BacktestInstance {
15045
15678
  * @param symbol - Trading pair symbol
15046
15679
  * @param strategyName - Strategy name to save report for
15047
15680
  * @param path - Optional directory path to save report (default: "./dump/backtest")
15681
+ * @param columns - Optional columns configuration for the report
15048
15682
  *
15049
15683
  * @example
15050
15684
  * ```typescript
@@ -15056,13 +15690,13 @@ class BacktestInstance {
15056
15690
  * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
15057
15691
  * ```
15058
15692
  */
15059
- this.dump = async (symbol, strategyName, path) => {
15693
+ this.dump = async (symbol, strategyName, path, columns) => {
15060
15694
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_DUMP, {
15061
15695
  symbol,
15062
15696
  strategyName,
15063
15697
  path,
15064
15698
  });
15065
- await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path);
15699
+ await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path, columns);
15066
15700
  };
15067
15701
  }
15068
15702
  }
@@ -15201,6 +15835,7 @@ class BacktestUtils {
15201
15835
  *
15202
15836
  * @param symbol - Trading pair symbol
15203
15837
  * @param strategyName - Strategy name to generate report for
15838
+ * @param columns - Optional columns configuration for the report
15204
15839
  * @returns Promise resolving to markdown formatted report string
15205
15840
  *
15206
15841
  * @example
@@ -15209,7 +15844,7 @@ class BacktestUtils {
15209
15844
  * console.log(markdown);
15210
15845
  * ```
15211
15846
  */
15212
- this.getReport = async (symbol, strategyName) => {
15847
+ this.getReport = async (symbol, strategyName, columns) => {
15213
15848
  backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
15214
15849
  {
15215
15850
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15217,7 +15852,7 @@ class BacktestUtils {
15217
15852
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_REPORT));
15218
15853
  }
15219
15854
  const instance = this._getInstance(symbol, strategyName);
15220
- return await instance.getReport(symbol, strategyName);
15855
+ return await instance.getReport(symbol, strategyName, columns);
15221
15856
  };
15222
15857
  /**
15223
15858
  * Saves strategy report to disk.
@@ -15225,6 +15860,7 @@ class BacktestUtils {
15225
15860
  * @param symbol - Trading pair symbol
15226
15861
  * @param strategyName - Strategy name to save report for
15227
15862
  * @param path - Optional directory path to save report (default: "./dump/backtest")
15863
+ * @param columns - Optional columns configuration for the report
15228
15864
  *
15229
15865
  * @example
15230
15866
  * ```typescript
@@ -15235,7 +15871,7 @@ class BacktestUtils {
15235
15871
  * await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
15236
15872
  * ```
15237
15873
  */
15238
- this.dump = async (symbol, strategyName, path) => {
15874
+ this.dump = async (symbol, strategyName, path, columns) => {
15239
15875
  backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_DUMP);
15240
15876
  {
15241
15877
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15243,7 +15879,7 @@ class BacktestUtils {
15243
15879
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_DUMP));
15244
15880
  }
15245
15881
  const instance = this._getInstance(symbol, strategyName);
15246
- return await instance.dump(symbol, strategyName, path);
15882
+ return await instance.dump(symbol, strategyName, path, columns);
15247
15883
  };
15248
15884
  /**
15249
15885
  * Lists all active backtest instances with their current status.
@@ -15523,6 +16159,7 @@ class LiveInstance {
15523
16159
  *
15524
16160
  * @param symbol - Trading pair symbol
15525
16161
  * @param strategyName - Strategy name to generate report for
16162
+ * @param columns - Optional columns configuration for the report
15526
16163
  * @returns Promise resolving to markdown formatted report string
15527
16164
  *
15528
16165
  * @example
@@ -15532,12 +16169,12 @@ class LiveInstance {
15532
16169
  * console.log(markdown);
15533
16170
  * ```
15534
16171
  */
15535
- this.getReport = async (symbol, strategyName) => {
16172
+ this.getReport = async (symbol, strategyName, columns) => {
15536
16173
  backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
15537
16174
  symbol,
15538
16175
  strategyName,
15539
16176
  });
15540
- return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
16177
+ return await backtest$1.liveMarkdownService.getReport(symbol, strategyName, columns);
15541
16178
  };
15542
16179
  /**
15543
16180
  * Saves strategy report to disk.
@@ -15545,6 +16182,7 @@ class LiveInstance {
15545
16182
  * @param symbol - Trading pair symbol
15546
16183
  * @param strategyName - Strategy name to save report for
15547
16184
  * @param path - Optional directory path to save report (default: "./dump/live")
16185
+ * @param columns - Optional columns configuration for the report
15548
16186
  *
15549
16187
  * @example
15550
16188
  * ```typescript
@@ -15556,13 +16194,13 @@ class LiveInstance {
15556
16194
  * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
15557
16195
  * ```
15558
16196
  */
15559
- this.dump = async (symbol, strategyName, path) => {
16197
+ this.dump = async (symbol, strategyName, path, columns) => {
15560
16198
  backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
15561
16199
  symbol,
15562
16200
  strategyName,
15563
16201
  path,
15564
16202
  });
15565
- await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
16203
+ await backtest$1.liveMarkdownService.dump(symbol, strategyName, path, columns);
15566
16204
  };
15567
16205
  }
15568
16206
  }
@@ -15712,6 +16350,7 @@ class LiveUtils {
15712
16350
  *
15713
16351
  * @param symbol - Trading pair symbol
15714
16352
  * @param strategyName - Strategy name to generate report for
16353
+ * @param columns - Optional columns configuration for the report
15715
16354
  * @returns Promise resolving to markdown formatted report string
15716
16355
  *
15717
16356
  * @example
@@ -15720,7 +16359,7 @@ class LiveUtils {
15720
16359
  * console.log(markdown);
15721
16360
  * ```
15722
16361
  */
15723
- this.getReport = async (symbol, strategyName) => {
16362
+ this.getReport = async (symbol, strategyName, columns) => {
15724
16363
  backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_GET_REPORT);
15725
16364
  {
15726
16365
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15728,7 +16367,7 @@ class LiveUtils {
15728
16367
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
15729
16368
  }
15730
16369
  const instance = this._getInstance(symbol, strategyName);
15731
- return await instance.getReport(symbol, strategyName);
16370
+ return await instance.getReport(symbol, strategyName, columns);
15732
16371
  };
15733
16372
  /**
15734
16373
  * Saves strategy report to disk.
@@ -15736,6 +16375,7 @@ class LiveUtils {
15736
16375
  * @param symbol - Trading pair symbol
15737
16376
  * @param strategyName - Strategy name to save report for
15738
16377
  * @param path - Optional directory path to save report (default: "./dump/live")
16378
+ * @param columns - Optional columns configuration for the report
15739
16379
  *
15740
16380
  * @example
15741
16381
  * ```typescript
@@ -15746,7 +16386,7 @@ class LiveUtils {
15746
16386
  * await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
15747
16387
  * ```
15748
16388
  */
15749
- this.dump = async (symbol, strategyName, path) => {
16389
+ this.dump = async (symbol, strategyName, path, columns) => {
15750
16390
  backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_DUMP);
15751
16391
  {
15752
16392
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15754,7 +16394,7 @@ class LiveUtils {
15754
16394
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
15755
16395
  }
15756
16396
  const instance = this._getInstance(symbol, strategyName);
15757
- return await instance.dump(symbol, strategyName, path);
16397
+ return await instance.dump(symbol, strategyName, path, columns);
15758
16398
  };
15759
16399
  /**
15760
16400
  * Lists all active live trading instances with their current status.
@@ -15853,6 +16493,7 @@ class ScheduleUtils {
15853
16493
  *
15854
16494
  * @param symbol - Trading pair symbol
15855
16495
  * @param strategyName - Strategy name to generate report for
16496
+ * @param columns - Optional columns configuration for the report
15856
16497
  * @returns Promise resolving to markdown formatted report string
15857
16498
  *
15858
16499
  * @example
@@ -15861,7 +16502,7 @@ class ScheduleUtils {
15861
16502
  * console.log(markdown);
15862
16503
  * ```
15863
16504
  */
15864
- this.getReport = async (symbol, strategyName) => {
16505
+ this.getReport = async (symbol, strategyName, columns) => {
15865
16506
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_REPORT, {
15866
16507
  symbol,
15867
16508
  strategyName,
@@ -15872,7 +16513,7 @@ class ScheduleUtils {
15872
16513
  riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
15873
16514
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT));
15874
16515
  }
15875
- return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName);
16516
+ return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName, columns);
15876
16517
  };
15877
16518
  /**
15878
16519
  * Saves strategy report to disk.
@@ -15880,6 +16521,7 @@ class ScheduleUtils {
15880
16521
  * @param symbol - Trading pair symbol
15881
16522
  * @param strategyName - Strategy name to save report for
15882
16523
  * @param path - Optional directory path to save report (default: "./dump/schedule")
16524
+ * @param columns - Optional columns configuration for the report
15883
16525
  *
15884
16526
  * @example
15885
16527
  * ```typescript
@@ -15890,7 +16532,7 @@ class ScheduleUtils {
15890
16532
  * await Schedule.dump("BTCUSDT", "my-strategy", "./custom/path");
15891
16533
  * ```
15892
16534
  */
15893
- this.dump = async (symbol, strategyName, path) => {
16535
+ this.dump = async (symbol, strategyName, path, columns) => {
15894
16536
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_DUMP, {
15895
16537
  symbol,
15896
16538
  strategyName,
@@ -15902,7 +16544,7 @@ class ScheduleUtils {
15902
16544
  riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
15903
16545
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP));
15904
16546
  }
15905
- await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path);
16547
+ await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path, columns);
15906
16548
  };
15907
16549
  }
15908
16550
  }
@@ -16002,6 +16644,7 @@ class Performance {
16002
16644
  *
16003
16645
  * @param symbol - Trading pair symbol
16004
16646
  * @param strategyName - Strategy name to generate report for
16647
+ * @param columns - Optional columns configuration for the report
16005
16648
  * @returns Markdown formatted report string
16006
16649
  *
16007
16650
  * @example
@@ -16014,14 +16657,14 @@ class Performance {
16014
16657
  * await fs.writeFile("performance-report.md", markdown);
16015
16658
  * ```
16016
16659
  */
16017
- static async getReport(symbol, strategyName) {
16660
+ static async getReport(symbol, strategyName, columns) {
16018
16661
  backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
16019
16662
  {
16020
16663
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16021
16664
  riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
16022
16665
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT));
16023
16666
  }
16024
- return backtest$1.performanceMarkdownService.getReport(symbol, strategyName);
16667
+ return backtest$1.performanceMarkdownService.getReport(symbol, strategyName, columns);
16025
16668
  }
16026
16669
  /**
16027
16670
  * Saves performance report to disk.
@@ -16032,6 +16675,7 @@ class Performance {
16032
16675
  * @param symbol - Trading pair symbol
16033
16676
  * @param strategyName - Strategy name to save report for
16034
16677
  * @param path - Optional custom directory path
16678
+ * @param columns - Optional columns configuration for the report
16035
16679
  *
16036
16680
  * @example
16037
16681
  * ```typescript
@@ -16042,14 +16686,14 @@ class Performance {
16042
16686
  * await Performance.dump("BTCUSDT", "my-strategy", "./reports/perf");
16043
16687
  * ```
16044
16688
  */
16045
- static async dump(symbol, strategyName, path = "./dump/performance") {
16689
+ static async dump(symbol, strategyName, path = "./dump/performance", columns) {
16046
16690
  backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_DUMP);
16047
16691
  {
16048
16692
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16049
16693
  riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
16050
16694
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP));
16051
16695
  }
16052
- return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path);
16696
+ return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path, columns);
16053
16697
  }
16054
16698
  }
16055
16699
 
@@ -16311,6 +16955,8 @@ class WalkerInstance {
16311
16955
  *
16312
16956
  * @param symbol - Trading symbol
16313
16957
  * @param walkerName - Walker name to generate report for
16958
+ * @param strategyColumns - Optional strategy columns configuration
16959
+ * @param pnlColumns - Optional PNL columns configuration
16314
16960
  * @returns Promise resolving to markdown formatted report string
16315
16961
  *
16316
16962
  * @example
@@ -16320,7 +16966,7 @@ class WalkerInstance {
16320
16966
  * console.log(markdown);
16321
16967
  * ```
16322
16968
  */
16323
- this.getReport = async (symbol, walkerName) => {
16969
+ this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
16324
16970
  backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_REPORT, {
16325
16971
  symbol,
16326
16972
  walkerName,
@@ -16329,7 +16975,7 @@ class WalkerInstance {
16329
16975
  return await backtest$1.walkerMarkdownService.getReport(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
16330
16976
  exchangeName: walkerSchema.exchangeName,
16331
16977
  frameName: walkerSchema.frameName,
16332
- });
16978
+ }, strategyColumns, pnlColumns);
16333
16979
  };
16334
16980
  /**
16335
16981
  * Saves walker report to disk.
@@ -16337,6 +16983,8 @@ class WalkerInstance {
16337
16983
  * @param symbol - Trading symbol
16338
16984
  * @param walkerName - Walker name to save report for
16339
16985
  * @param path - Optional directory path to save report (default: "./dump/walker")
16986
+ * @param strategyColumns - Optional strategy columns configuration
16987
+ * @param pnlColumns - Optional PNL columns configuration
16340
16988
  *
16341
16989
  * @example
16342
16990
  * ```typescript
@@ -16348,7 +16996,7 @@ class WalkerInstance {
16348
16996
  * await instance.dump("BTCUSDT", "my-walker", "./custom/path");
16349
16997
  * ```
16350
16998
  */
16351
- this.dump = async (symbol, walkerName, path) => {
16999
+ this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
16352
17000
  backtest$1.loggerService.info(WALKER_METHOD_NAME_DUMP, {
16353
17001
  symbol,
16354
17002
  walkerName,
@@ -16358,7 +17006,7 @@ class WalkerInstance {
16358
17006
  await backtest$1.walkerMarkdownService.dump(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
16359
17007
  exchangeName: walkerSchema.exchangeName,
16360
17008
  frameName: walkerSchema.frameName,
16361
- }, path);
17009
+ }, path, strategyColumns, pnlColumns);
16362
17010
  };
16363
17011
  }
16364
17012
  }
@@ -16507,6 +17155,8 @@ class WalkerUtils {
16507
17155
  *
16508
17156
  * @param symbol - Trading symbol
16509
17157
  * @param walkerName - Walker name to generate report for
17158
+ * @param strategyColumns - Optional strategy columns configuration
17159
+ * @param pnlColumns - Optional PNL columns configuration
16510
17160
  * @returns Promise resolving to markdown formatted report string
16511
17161
  *
16512
17162
  * @example
@@ -16515,7 +17165,7 @@ class WalkerUtils {
16515
17165
  * console.log(markdown);
16516
17166
  * ```
16517
17167
  */
16518
- this.getReport = async (symbol, walkerName) => {
17168
+ this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
16519
17169
  backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_GET_REPORT);
16520
17170
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
16521
17171
  for (const strategyName of walkerSchema.strategies) {
@@ -16525,7 +17175,7 @@ class WalkerUtils {
16525
17175
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT));
16526
17176
  }
16527
17177
  const instance = this._getInstance(symbol, walkerName);
16528
- return await instance.getReport(symbol, walkerName);
17178
+ return await instance.getReport(symbol, walkerName, strategyColumns, pnlColumns);
16529
17179
  };
16530
17180
  /**
16531
17181
  * Saves walker report to disk.
@@ -16533,6 +17183,8 @@ class WalkerUtils {
16533
17183
  * @param symbol - Trading symbol
16534
17184
  * @param walkerName - Walker name to save report for
16535
17185
  * @param path - Optional directory path to save report (default: "./dump/walker")
17186
+ * @param strategyColumns - Optional strategy columns configuration
17187
+ * @param pnlColumns - Optional PNL columns configuration
16536
17188
  *
16537
17189
  * @example
16538
17190
  * ```typescript
@@ -16543,7 +17195,7 @@ class WalkerUtils {
16543
17195
  * await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
16544
17196
  * ```
16545
17197
  */
16546
- this.dump = async (symbol, walkerName, path) => {
17198
+ this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
16547
17199
  backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_DUMP);
16548
17200
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
16549
17201
  for (const strategyName of walkerSchema.strategies) {
@@ -16553,7 +17205,7 @@ class WalkerUtils {
16553
17205
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP));
16554
17206
  }
16555
17207
  const instance = this._getInstance(symbol, walkerName);
16556
- return await instance.dump(symbol, walkerName, path);
17208
+ return await instance.dump(symbol, walkerName, path, strategyColumns, pnlColumns);
16557
17209
  };
16558
17210
  /**
16559
17211
  * Lists all active walker instances with their current status.
@@ -16659,6 +17311,7 @@ class HeatUtils {
16659
17311
  * Symbols are sorted by Total PNL descending.
16660
17312
  *
16661
17313
  * @param strategyName - Strategy name to generate heatmap report for
17314
+ * @param columns - Optional columns configuration for the report
16662
17315
  * @returns Promise resolving to markdown formatted report string
16663
17316
  *
16664
17317
  * @example
@@ -16677,7 +17330,7 @@ class HeatUtils {
16677
17330
  * // ...
16678
17331
  * ```
16679
17332
  */
16680
- this.getReport = async (strategyName) => {
17333
+ this.getReport = async (strategyName, columns) => {
16681
17334
  backtest$1.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName });
16682
17335
  backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_GET_REPORT);
16683
17336
  {
@@ -16685,7 +17338,7 @@ class HeatUtils {
16685
17338
  riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
16686
17339
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT));
16687
17340
  }
16688
- return await backtest$1.heatMarkdownService.getReport(strategyName);
17341
+ return await backtest$1.heatMarkdownService.getReport(strategyName, columns);
16689
17342
  };
16690
17343
  /**
16691
17344
  * Saves heatmap report to disk for a strategy.
@@ -16695,6 +17348,7 @@ class HeatUtils {
16695
17348
  *
16696
17349
  * @param strategyName - Strategy name to save heatmap report for
16697
17350
  * @param path - Optional directory path to save report (default: "./dump/heatmap")
17351
+ * @param columns - Optional columns configuration for the report
16698
17352
  *
16699
17353
  * @example
16700
17354
  * ```typescript
@@ -16705,7 +17359,7 @@ class HeatUtils {
16705
17359
  * await Heat.dump("my-strategy", "./reports");
16706
17360
  * ```
16707
17361
  */
16708
- this.dump = async (strategyName, path) => {
17362
+ this.dump = async (strategyName, path, columns) => {
16709
17363
  backtest$1.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName, path });
16710
17364
  backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_DUMP);
16711
17365
  {
@@ -16713,7 +17367,7 @@ class HeatUtils {
16713
17367
  riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
16714
17368
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP));
16715
17369
  }
16716
- await backtest$1.heatMarkdownService.dump(strategyName, path);
17370
+ await backtest$1.heatMarkdownService.dump(strategyName, path, columns);
16717
17371
  };
16718
17372
  }
16719
17373
  }
@@ -17013,7 +17667,7 @@ class PartialUtils {
17013
17667
  *
17014
17668
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17015
17669
  * @param strategyName - Strategy name (e.g., "my-strategy")
17016
- * @returns Promise resolving to PartialStatistics object with counts and event list
17670
+ * @returns Promise resolving to PartialStatisticsModel object with counts and event list
17017
17671
  *
17018
17672
  * @example
17019
17673
  * ```typescript
@@ -17057,6 +17711,7 @@ class PartialUtils {
17057
17711
  *
17058
17712
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17059
17713
  * @param strategyName - Strategy name (e.g., "my-strategy")
17714
+ * @param columns - Optional columns configuration for the report
17060
17715
  * @returns Promise resolving to markdown formatted report string
17061
17716
  *
17062
17717
  * @example
@@ -17077,7 +17732,7 @@ class PartialUtils {
17077
17732
  * // **Loss events:** 1
17078
17733
  * ```
17079
17734
  */
17080
- this.getReport = async (symbol, strategyName) => {
17735
+ this.getReport = async (symbol, strategyName, columns) => {
17081
17736
  backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName });
17082
17737
  backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
17083
17738
  {
@@ -17085,7 +17740,7 @@ class PartialUtils {
17085
17740
  riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
17086
17741
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT));
17087
17742
  }
17088
- return await backtest$1.partialMarkdownService.getReport(symbol, strategyName);
17743
+ return await backtest$1.partialMarkdownService.getReport(symbol, strategyName, columns);
17089
17744
  };
17090
17745
  /**
17091
17746
  * Generates and saves markdown report to file.
@@ -17102,6 +17757,7 @@ class PartialUtils {
17102
17757
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17103
17758
  * @param strategyName - Strategy name (e.g., "my-strategy")
17104
17759
  * @param path - Output directory path (default: "./dump/partial")
17760
+ * @param columns - Optional columns configuration for the report
17105
17761
  * @returns Promise that resolves when file is written
17106
17762
  *
17107
17763
  * @example
@@ -17118,7 +17774,7 @@ class PartialUtils {
17118
17774
  * }
17119
17775
  * ```
17120
17776
  */
17121
- this.dump = async (symbol, strategyName, path) => {
17777
+ this.dump = async (symbol, strategyName, path, columns) => {
17122
17778
  backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName, path });
17123
17779
  backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_DUMP);
17124
17780
  {
@@ -17126,7 +17782,7 @@ class PartialUtils {
17126
17782
  riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
17127
17783
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP));
17128
17784
  }
17129
- await backtest$1.partialMarkdownService.dump(symbol, strategyName, path);
17785
+ await backtest$1.partialMarkdownService.dump(symbol, strategyName, path, columns);
17130
17786
  };
17131
17787
  }
17132
17788
  }
@@ -17227,4 +17883,4 @@ class ConstantUtils {
17227
17883
  */
17228
17884
  const Constant = new ConstantUtils();
17229
17885
 
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 };
17886
+ 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 };