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.cjs CHANGED
@@ -122,6 +122,1081 @@ const GLOBAL_CONFIG = {
122
122
  };
123
123
  const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
124
124
 
125
+ /**
126
+ * Converts markdown content to plain text with minimal formatting
127
+ * @param content - Markdown string to convert
128
+ * @returns Plain text representation
129
+ */
130
+ const toPlainString = (content) => {
131
+ if (!content) {
132
+ return "";
133
+ }
134
+ let text = content;
135
+ // Remove code blocks
136
+ text = text.replace(/```[\s\S]*?```/g, "");
137
+ text = text.replace(/`([^`]+)`/g, "$1");
138
+ // Remove images
139
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
140
+ // Convert links to text only (keep link text, remove URL)
141
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
142
+ // Remove headers (convert to plain text)
143
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
144
+ // Remove bold and italic markers
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
+ text = text.replace(/__(.+?)__/g, "$1");
150
+ text = text.replace(/_(.+?)_/g, "$1");
151
+ // Remove strikethrough
152
+ text = text.replace(/~~(.+?)~~/g, "$1");
153
+ // Convert lists to plain text with bullets
154
+ text = text.replace(/^\s*[-*+]\s+/gm, "• ");
155
+ text = text.replace(/^\s*\d+\.\s+/gm, "• ");
156
+ // Remove blockquotes
157
+ text = text.replace(/^\s*>\s+/gm, "");
158
+ // Remove horizontal rules
159
+ text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
160
+ // Remove HTML tags
161
+ text = text.replace(/<[^>]+>/g, "");
162
+ // Remove excessive whitespace and normalize line breaks
163
+ text = text.replace(/\n[\s\n]*\n/g, "\n");
164
+ text = text.replace(/[ \t]+/g, " ");
165
+ // Remove all newline characters
166
+ text = text.replace(/\n/g, " ");
167
+ // Remove excessive spaces after newline removal
168
+ text = text.replace(/\s+/g, " ");
169
+ return text.trim();
170
+ };
171
+
172
+ /**
173
+ * Column configuration for backtest markdown reports.
174
+ *
175
+ * Defines the table structure for displaying closed trading signals in backtest reports.
176
+ * Each column specifies how to format and display specific signal data fields.
177
+ *
178
+ * Used by {@link BacktestMarkdownService} to generate markdown tables showing:
179
+ * - Signal identification (ID, symbol, position)
180
+ * - Price levels (open, close, take profit, stop loss)
181
+ * - Performance metrics (PNL percentage, close reason)
182
+ * - Timing information (duration, timestamps)
183
+ *
184
+ * @remarks
185
+ * Columns can be conditionally visible based on {@link GLOBAL_CONFIG} settings.
186
+ * For example, the "note" column visibility is controlled by `CC_REPORT_SHOW_SIGNAL_NOTE`.
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * import { backtest_columns } from "./assets/backtest.columns";
191
+ *
192
+ * // Use with BacktestMarkdownService
193
+ * const service = new BacktestMarkdownService();
194
+ * await service.getReport("BTCUSDT", "my-strategy", backtest_columns);
195
+ *
196
+ * // Or customize columns
197
+ * const customColumns = backtest_columns.filter(col => col.key !== "note");
198
+ * await service.getReport("BTCUSDT", "my-strategy", customColumns);
199
+ * ```
200
+ *
201
+ * @see {@link BacktestMarkdownService} for usage in report generation
202
+ * @see {@link ColumnModel} for column interface definition
203
+ * @see {@link IStrategyTickResultClosed} for data structure
204
+ */
205
+ const backtest_columns = [
206
+ {
207
+ key: "signalId",
208
+ label: "Signal ID",
209
+ format: (data) => data.signal.id,
210
+ isVisible: () => true,
211
+ },
212
+ {
213
+ key: "symbol",
214
+ label: "Symbol",
215
+ format: (data) => data.signal.symbol,
216
+ isVisible: () => true,
217
+ },
218
+ {
219
+ key: "position",
220
+ label: "Position",
221
+ format: (data) => data.signal.position.toUpperCase(),
222
+ isVisible: () => true,
223
+ },
224
+ {
225
+ key: "note",
226
+ label: "Note",
227
+ format: (data) => toPlainString(data.signal.note ?? "N/A"),
228
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
229
+ },
230
+ {
231
+ key: "openPrice",
232
+ label: "Open Price",
233
+ format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
234
+ isVisible: () => true,
235
+ },
236
+ {
237
+ key: "closePrice",
238
+ label: "Close Price",
239
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
240
+ isVisible: () => true,
241
+ },
242
+ {
243
+ key: "takeProfit",
244
+ label: "Take Profit",
245
+ format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
246
+ isVisible: () => true,
247
+ },
248
+ {
249
+ key: "stopLoss",
250
+ label: "Stop Loss",
251
+ format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
252
+ isVisible: () => true,
253
+ },
254
+ {
255
+ key: "pnl",
256
+ label: "PNL (net)",
257
+ format: (data) => {
258
+ const pnlPercentage = data.pnl.pnlPercentage;
259
+ return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
260
+ },
261
+ isVisible: () => true,
262
+ },
263
+ {
264
+ key: "closeReason",
265
+ label: "Close Reason",
266
+ format: (data) => data.closeReason,
267
+ isVisible: () => true,
268
+ },
269
+ {
270
+ key: "duration",
271
+ label: "Duration (min)",
272
+ format: (data) => {
273
+ const durationMs = data.closeTimestamp - data.signal.pendingAt;
274
+ const durationMin = Math.round(durationMs / 60000);
275
+ return `${durationMin}`;
276
+ },
277
+ isVisible: () => true,
278
+ },
279
+ {
280
+ key: "openTimestamp",
281
+ label: "Open Time",
282
+ format: (data) => new Date(data.signal.pendingAt).toISOString(),
283
+ isVisible: () => true,
284
+ },
285
+ {
286
+ key: "closeTimestamp",
287
+ label: "Close Time",
288
+ format: (data) => new Date(data.closeTimestamp).toISOString(),
289
+ isVisible: () => true,
290
+ },
291
+ ];
292
+
293
+ /**
294
+ * Column configuration for portfolio heatmap markdown reports.
295
+ *
296
+ * Defines the table structure for displaying aggregated per-symbol statistics in portfolio heatmap reports.
297
+ * Each column specifies how to format and display portfolio performance metrics across different trading symbols.
298
+ *
299
+ * Used by {@link HeatMarkdownService} to generate markdown tables showing:
300
+ * - Symbol identification
301
+ * - Performance metrics (Total PNL, Sharpe Ratio, Profit Factor, Expectancy)
302
+ * - Risk metrics (Win Rate, Average Win/Loss, Max Drawdown)
303
+ * - Trading activity (Total Trades, Win/Loss Streaks)
304
+ *
305
+ * @remarks
306
+ * This configuration is used to create portfolio-wide views that aggregate statistics per symbol.
307
+ * The heatmap service automatically sorts symbols by Sharpe Ratio (best performers first).
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * import { heat_columns } from "./assets/heat.columns";
312
+ *
313
+ * // Use with HeatMarkdownService
314
+ * const service = new HeatMarkdownService();
315
+ * await service.getReport("my-strategy", heat_columns);
316
+ *
317
+ * // Or customize to show only key metrics
318
+ * const customColumns = heat_columns.filter(col =>
319
+ * ["symbol", "totalPnl", "sharpeRatio", "totalTrades"].includes(col.key)
320
+ * );
321
+ * await service.getReport("my-strategy", customColumns);
322
+ * ```
323
+ *
324
+ * @see {@link HeatMarkdownService} for usage in report generation
325
+ * @see {@link ColumnModel} for column interface definition
326
+ * @see {@link IHeatmapRow} for data structure
327
+ */
328
+ const heat_columns = [
329
+ {
330
+ key: "symbol",
331
+ label: "Symbol",
332
+ format: (data) => data.symbol,
333
+ isVisible: () => true,
334
+ },
335
+ {
336
+ key: "totalPnl",
337
+ label: "Total PNL",
338
+ format: (data) => data.totalPnl !== null ? functoolsKit.str(data.totalPnl, "%+.2f%%") : "N/A",
339
+ isVisible: () => true,
340
+ },
341
+ {
342
+ key: "sharpeRatio",
343
+ label: "Sharpe",
344
+ format: (data) => data.sharpeRatio !== null ? functoolsKit.str(data.sharpeRatio, "%.2f") : "N/A",
345
+ isVisible: () => true,
346
+ },
347
+ {
348
+ key: "profitFactor",
349
+ label: "PF",
350
+ format: (data) => data.profitFactor !== null ? functoolsKit.str(data.profitFactor, "%.2f") : "N/A",
351
+ isVisible: () => true,
352
+ },
353
+ {
354
+ key: "expectancy",
355
+ label: "Expect",
356
+ format: (data) => data.expectancy !== null ? functoolsKit.str(data.expectancy, "%+.2f%%") : "N/A",
357
+ isVisible: () => true,
358
+ },
359
+ {
360
+ key: "winRate",
361
+ label: "WR",
362
+ format: (data) => data.winRate !== null ? functoolsKit.str(data.winRate, "%.1f%%") : "N/A",
363
+ isVisible: () => true,
364
+ },
365
+ {
366
+ key: "avgWin",
367
+ label: "Avg Win",
368
+ format: (data) => data.avgWin !== null ? functoolsKit.str(data.avgWin, "%+.2f%%") : "N/A",
369
+ isVisible: () => true,
370
+ },
371
+ {
372
+ key: "avgLoss",
373
+ label: "Avg Loss",
374
+ format: (data) => data.avgLoss !== null ? functoolsKit.str(data.avgLoss, "%+.2f%%") : "N/A",
375
+ isVisible: () => true,
376
+ },
377
+ {
378
+ key: "maxDrawdown",
379
+ label: "Max DD",
380
+ format: (data) => data.maxDrawdown !== null ? functoolsKit.str(-data.maxDrawdown, "%.2f%%") : "N/A",
381
+ isVisible: () => true,
382
+ },
383
+ {
384
+ key: "maxWinStreak",
385
+ label: "W Streak",
386
+ format: (data) => data.maxWinStreak.toString(),
387
+ isVisible: () => true,
388
+ },
389
+ {
390
+ key: "maxLossStreak",
391
+ label: "L Streak",
392
+ format: (data) => data.maxLossStreak.toString(),
393
+ isVisible: () => true,
394
+ },
395
+ {
396
+ key: "totalTrades",
397
+ label: "Trades",
398
+ format: (data) => data.totalTrades.toString(),
399
+ isVisible: () => true,
400
+ },
401
+ ];
402
+
403
+ /**
404
+ * Column configuration for live trading markdown reports.
405
+ *
406
+ * Defines the table structure for displaying real-time trading events in live trading reports.
407
+ * Each column specifies how to format and display live trading event data fields.
408
+ *
409
+ * Used by {@link LiveMarkdownService} to generate markdown tables showing:
410
+ * - Event information (timestamp, action type)
411
+ * - Signal identification (symbol, signal ID, position)
412
+ * - Price data (current price, open price, take profit, stop loss)
413
+ * - Performance metrics (PNL percentage, close reason, duration)
414
+ * - Position tracking (percentage to TP/SL)
415
+ *
416
+ * @remarks
417
+ * This configuration tracks all event types: idle, opened, active, and closed signals.
418
+ * The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
419
+ *
420
+ * @example
421
+ * ```typescript
422
+ * import { live_columns } from "./assets/live.columns";
423
+ *
424
+ * // Use with LiveMarkdownService
425
+ * const service = new LiveMarkdownService();
426
+ * await service.getReport("BTCUSDT", "my-strategy", live_columns);
427
+ *
428
+ * // Or customize for minimal display
429
+ * const customColumns = live_columns.filter(col =>
430
+ * ["timestamp", "action", "symbol", "pnl"].includes(col.key)
431
+ * );
432
+ * await service.getReport("BTCUSDT", "my-strategy", customColumns);
433
+ * ```
434
+ *
435
+ * @see {@link LiveMarkdownService} for usage in report generation
436
+ * @see {@link ColumnModel} for column interface definition
437
+ * @see {@link TickEvent} for data structure
438
+ */
439
+ const live_columns = [
440
+ {
441
+ key: "timestamp",
442
+ label: "Timestamp",
443
+ format: (data) => new Date(data.timestamp).toISOString(),
444
+ isVisible: () => true,
445
+ },
446
+ {
447
+ key: "action",
448
+ label: "Action",
449
+ format: (data) => data.action.toUpperCase(),
450
+ isVisible: () => true,
451
+ },
452
+ {
453
+ key: "symbol",
454
+ label: "Symbol",
455
+ format: (data) => data.symbol ?? "N/A",
456
+ isVisible: () => true,
457
+ },
458
+ {
459
+ key: "signalId",
460
+ label: "Signal ID",
461
+ format: (data) => data.signalId ?? "N/A",
462
+ isVisible: () => true,
463
+ },
464
+ {
465
+ key: "position",
466
+ label: "Position",
467
+ format: (data) => data.position?.toUpperCase() ?? "N/A",
468
+ isVisible: () => true,
469
+ },
470
+ {
471
+ key: "note",
472
+ label: "Note",
473
+ format: (data) => toPlainString(data.note ?? "N/A"),
474
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
475
+ },
476
+ {
477
+ key: "currentPrice",
478
+ label: "Current Price",
479
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
480
+ isVisible: () => true,
481
+ },
482
+ {
483
+ key: "openPrice",
484
+ label: "Open Price",
485
+ format: (data) => data.openPrice !== undefined ? `${data.openPrice.toFixed(8)} USD` : "N/A",
486
+ isVisible: () => true,
487
+ },
488
+ {
489
+ key: "takeProfit",
490
+ label: "Take Profit",
491
+ format: (data) => data.takeProfit !== undefined
492
+ ? `${data.takeProfit.toFixed(8)} USD`
493
+ : "N/A",
494
+ isVisible: () => true,
495
+ },
496
+ {
497
+ key: "stopLoss",
498
+ label: "Stop Loss",
499
+ format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
500
+ isVisible: () => true,
501
+ },
502
+ {
503
+ key: "percentTp",
504
+ label: "% to TP",
505
+ format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
506
+ isVisible: () => true,
507
+ },
508
+ {
509
+ key: "percentSl",
510
+ label: "% to SL",
511
+ format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
512
+ isVisible: () => true,
513
+ },
514
+ {
515
+ key: "pnl",
516
+ label: "PNL (net)",
517
+ format: (data) => {
518
+ if (data.pnl === undefined)
519
+ return "N/A";
520
+ return `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`;
521
+ },
522
+ isVisible: () => true,
523
+ },
524
+ {
525
+ key: "closeReason",
526
+ label: "Close Reason",
527
+ format: (data) => data.closeReason ?? "N/A",
528
+ isVisible: () => true,
529
+ },
530
+ {
531
+ key: "duration",
532
+ label: "Duration (min)",
533
+ format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
534
+ isVisible: () => true,
535
+ },
536
+ ];
537
+
538
+ /**
539
+ * Column configuration for partial profit/loss markdown reports.
540
+ *
541
+ * Defines the table structure for displaying partial position exit events in trading reports.
542
+ * Each column specifies how to format and display partial profit and loss level events.
543
+ *
544
+ * Used by {@link PartialMarkdownService} to generate markdown tables showing:
545
+ * - Event information (action type: profit or loss)
546
+ * - Signal identification (symbol, strategy name, signal ID, position)
547
+ * - Exit level information (percentage level reached)
548
+ * - Price data (current price at partial exit)
549
+ * - Timing information (timestamp, mode: backtest or live)
550
+ *
551
+ * @remarks
552
+ * This configuration tracks partial position exits at predefined profit/loss levels.
553
+ * The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
554
+ * Useful for analyzing risk management strategies and partial exit performance.
555
+ *
556
+ * @example
557
+ * ```typescript
558
+ * import { partial_columns } from "./assets/partial.columns";
559
+ *
560
+ * // Use with PartialMarkdownService
561
+ * const service = new PartialMarkdownService();
562
+ * await service.getReport("BTCUSDT", "my-strategy", partial_columns);
563
+ *
564
+ * // Or customize to show only key fields
565
+ * const customColumns = partial_columns.filter(col =>
566
+ * ["action", "symbol", "level", "timestamp"].includes(col.key)
567
+ * );
568
+ * await service.getReport("BTCUSDT", "my-strategy", customColumns);
569
+ * ```
570
+ *
571
+ * @see {@link PartialMarkdownService} for usage in report generation
572
+ * @see {@link ColumnModel} for column interface definition
573
+ * @see {@link PartialEvent} for data structure
574
+ */
575
+ const partial_columns = [
576
+ {
577
+ key: "action",
578
+ label: "Action",
579
+ format: (data) => data.action.toUpperCase(),
580
+ isVisible: () => true,
581
+ },
582
+ {
583
+ key: "symbol",
584
+ label: "Symbol",
585
+ format: (data) => data.symbol,
586
+ isVisible: () => true,
587
+ },
588
+ {
589
+ key: "strategyName",
590
+ label: "Strategy",
591
+ format: (data) => data.strategyName,
592
+ isVisible: () => true,
593
+ },
594
+ {
595
+ key: "signalId",
596
+ label: "Signal ID",
597
+ format: (data) => data.signalId,
598
+ isVisible: () => true,
599
+ },
600
+ {
601
+ key: "position",
602
+ label: "Position",
603
+ format: (data) => data.position.toUpperCase(),
604
+ isVisible: () => true,
605
+ },
606
+ {
607
+ key: "level",
608
+ label: "Level %",
609
+ format: (data) => data.action === "profit" ? `+${data.level}%` : `-${data.level}%`,
610
+ isVisible: () => true,
611
+ },
612
+ {
613
+ key: "currentPrice",
614
+ label: "Current Price",
615
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
616
+ isVisible: () => true,
617
+ },
618
+ {
619
+ key: "timestamp",
620
+ label: "Timestamp",
621
+ format: (data) => new Date(data.timestamp).toISOString(),
622
+ isVisible: () => true,
623
+ },
624
+ {
625
+ key: "mode",
626
+ label: "Mode",
627
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
628
+ isVisible: () => true,
629
+ },
630
+ ];
631
+
632
+ /**
633
+ * Column configuration for performance metrics markdown reports.
634
+ *
635
+ * Defines the table structure for displaying performance statistics and timing metrics in performance reports.
636
+ * Each column specifies how to format and display aggregated performance data for different metric types.
637
+ *
638
+ * Used by {@link PerformanceMarkdownService} to generate markdown tables showing:
639
+ * - Metric identification (metric type)
640
+ * - Execution statistics (count, total duration, average, min, max)
641
+ * - Distribution metrics (standard deviation, median, percentiles)
642
+ * - Wait time statistics (average, min, max wait times between events)
643
+ *
644
+ * @remarks
645
+ * This configuration helps identify performance bottlenecks in strategy execution.
646
+ * The service automatically sorts metrics by total duration to highlight the slowest operations.
647
+ * All durations are measured in milliseconds.
648
+ *
649
+ * @example
650
+ * ```typescript
651
+ * import { performance_columns } from "./assets/performance.columns";
652
+ *
653
+ * // Use with PerformanceMarkdownService
654
+ * const service = new PerformanceMarkdownService();
655
+ * await service.getReport("BTCUSDT", "my-strategy", performance_columns);
656
+ *
657
+ * // Or customize for bottleneck analysis
658
+ * const customColumns = performance_columns.filter(col =>
659
+ * ["metricType", "count", "avgDuration", "maxDuration", "p95"].includes(col.key)
660
+ * );
661
+ * await service.getReport("BTCUSDT", "my-strategy", customColumns);
662
+ * ```
663
+ *
664
+ * @see {@link PerformanceMarkdownService} for usage in report generation
665
+ * @see {@link ColumnModel} for column interface definition
666
+ * @see {@link MetricStats} for data structure
667
+ */
668
+ const performance_columns = [
669
+ {
670
+ key: "metricType",
671
+ label: "Metric Type",
672
+ format: (data) => data.metricType,
673
+ isVisible: () => true,
674
+ },
675
+ {
676
+ key: "count",
677
+ label: "Count",
678
+ format: (data) => data.count.toString(),
679
+ isVisible: () => true,
680
+ },
681
+ {
682
+ key: "totalDuration",
683
+ label: "Total (ms)",
684
+ format: (data) => data.totalDuration.toFixed(2),
685
+ isVisible: () => true,
686
+ },
687
+ {
688
+ key: "avgDuration",
689
+ label: "Avg (ms)",
690
+ format: (data) => data.avgDuration.toFixed(2),
691
+ isVisible: () => true,
692
+ },
693
+ {
694
+ key: "minDuration",
695
+ label: "Min (ms)",
696
+ format: (data) => data.minDuration.toFixed(2),
697
+ isVisible: () => true,
698
+ },
699
+ {
700
+ key: "maxDuration",
701
+ label: "Max (ms)",
702
+ format: (data) => data.maxDuration.toFixed(2),
703
+ isVisible: () => true,
704
+ },
705
+ {
706
+ key: "stdDev",
707
+ label: "Std Dev (ms)",
708
+ format: (data) => data.stdDev.toFixed(2),
709
+ isVisible: () => true,
710
+ },
711
+ {
712
+ key: "median",
713
+ label: "Median (ms)",
714
+ format: (data) => data.median.toFixed(2),
715
+ isVisible: () => true,
716
+ },
717
+ {
718
+ key: "p95",
719
+ label: "P95 (ms)",
720
+ format: (data) => data.p95.toFixed(2),
721
+ isVisible: () => true,
722
+ },
723
+ {
724
+ key: "p99",
725
+ label: "P99 (ms)",
726
+ format: (data) => data.p99.toFixed(2),
727
+ isVisible: () => true,
728
+ },
729
+ {
730
+ key: "avgWaitTime",
731
+ label: "Avg Wait (ms)",
732
+ format: (data) => data.avgWaitTime.toFixed(2),
733
+ isVisible: () => true,
734
+ },
735
+ {
736
+ key: "minWaitTime",
737
+ label: "Min Wait (ms)",
738
+ format: (data) => data.minWaitTime.toFixed(2),
739
+ isVisible: () => true,
740
+ },
741
+ {
742
+ key: "maxWaitTime",
743
+ label: "Max Wait (ms)",
744
+ format: (data) => data.maxWaitTime.toFixed(2),
745
+ isVisible: () => true,
746
+ },
747
+ ];
748
+
749
+ /**
750
+ * Column configuration for risk management markdown reports.
751
+ *
752
+ * Defines the table structure for displaying risk rejection events in risk management reports.
753
+ * Each column specifies how to format and display signals that were rejected due to risk limits.
754
+ *
755
+ * Used by {@link RiskMarkdownService} to generate markdown tables showing:
756
+ * - Signal identification (symbol, strategy name, signal ID, position)
757
+ * - Exchange information (exchange name, active position count)
758
+ * - Price data (open price, take profit, stop loss, current price)
759
+ * - Rejection details (rejection reason/comment, timestamp)
760
+ *
761
+ * @remarks
762
+ * This configuration helps analyze when and why the risk management system rejected signals.
763
+ * The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
764
+ * Useful for tuning risk parameters and understanding risk control effectiveness.
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * import { risk_columns } from "./assets/risk.columns";
769
+ *
770
+ * // Use with RiskMarkdownService
771
+ * const service = new RiskMarkdownService();
772
+ * await service.getReport("BTCUSDT", "my-strategy", risk_columns);
773
+ *
774
+ * // Or customize to focus on rejection reasons
775
+ * const customColumns = risk_columns.filter(col =>
776
+ * ["symbol", "strategyName", "comment", "activePositionCount", "timestamp"].includes(col.key)
777
+ * );
778
+ * await service.getReport("BTCUSDT", "my-strategy", customColumns);
779
+ * ```
780
+ *
781
+ * @see {@link RiskMarkdownService} for usage in report generation
782
+ * @see {@link ColumnModel} for column interface definition
783
+ * @see {@link RiskEvent} for data structure
784
+ */
785
+ const risk_columns = [
786
+ {
787
+ key: "symbol",
788
+ label: "Symbol",
789
+ format: (data) => data.symbol,
790
+ isVisible: () => true,
791
+ },
792
+ {
793
+ key: "strategyName",
794
+ label: "Strategy",
795
+ format: (data) => data.strategyName,
796
+ isVisible: () => true,
797
+ },
798
+ {
799
+ key: "signalId",
800
+ label: "Signal ID",
801
+ format: (data) => data.pendingSignal.id || "N/A",
802
+ isVisible: () => true,
803
+ },
804
+ {
805
+ key: "position",
806
+ label: "Position",
807
+ format: (data) => data.pendingSignal.position.toUpperCase(),
808
+ isVisible: () => true,
809
+ },
810
+ {
811
+ key: "note",
812
+ label: "Note",
813
+ format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
814
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
815
+ },
816
+ {
817
+ key: "exchangeName",
818
+ label: "Exchange",
819
+ format: (data) => data.exchangeName,
820
+ isVisible: () => true,
821
+ },
822
+ {
823
+ key: "openPrice",
824
+ label: "Open Price",
825
+ format: (data) => data.pendingSignal.priceOpen !== undefined
826
+ ? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
827
+ : "N/A",
828
+ isVisible: () => true,
829
+ },
830
+ {
831
+ key: "takeProfit",
832
+ label: "Take Profit",
833
+ format: (data) => data.pendingSignal.priceTakeProfit !== undefined
834
+ ? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
835
+ : "N/A",
836
+ isVisible: () => true,
837
+ },
838
+ {
839
+ key: "stopLoss",
840
+ label: "Stop Loss",
841
+ format: (data) => data.pendingSignal.priceStopLoss !== undefined
842
+ ? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
843
+ : "N/A",
844
+ isVisible: () => true,
845
+ },
846
+ {
847
+ key: "currentPrice",
848
+ label: "Current Price",
849
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
850
+ isVisible: () => true,
851
+ },
852
+ {
853
+ key: "activePositionCount",
854
+ label: "Active Positions",
855
+ format: (data) => data.activePositionCount.toString(),
856
+ isVisible: () => true,
857
+ },
858
+ {
859
+ key: "comment",
860
+ label: "Reason",
861
+ format: (data) => data.comment,
862
+ isVisible: () => true,
863
+ },
864
+ {
865
+ key: "timestamp",
866
+ label: "Timestamp",
867
+ format: (data) => new Date(data.timestamp).toISOString(),
868
+ isVisible: () => true,
869
+ },
870
+ ];
871
+
872
+ /**
873
+ * Column configuration for scheduled signals markdown reports.
874
+ *
875
+ * Defines the table structure for displaying scheduled, opened, and cancelled signal events.
876
+ * Each column specifies how to format and display signal scheduling and activation data.
877
+ *
878
+ * Used by {@link ScheduleMarkdownService} to generate markdown tables showing:
879
+ * - Event information (timestamp, action: scheduled/opened/cancelled)
880
+ * - Signal identification (symbol, signal ID, position)
881
+ * - Price data (current price, entry price, take profit, stop loss)
882
+ * - Timing information (wait time in minutes before activation or cancellation)
883
+ *
884
+ * @remarks
885
+ * This configuration tracks the lifecycle of scheduled signals from creation to activation or cancellation.
886
+ * The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
887
+ * Helps analyze signal scheduling effectiveness and cancellation patterns.
888
+ *
889
+ * @example
890
+ * ```typescript
891
+ * import { schedule_columns } from "./assets/schedule.columns";
892
+ *
893
+ * // Use with ScheduleMarkdownService
894
+ * const service = new ScheduleMarkdownService();
895
+ * await service.getReport("BTCUSDT", "my-strategy", schedule_columns);
896
+ *
897
+ * // Or customize for timing analysis
898
+ * const customColumns = schedule_columns.filter(col =>
899
+ * ["timestamp", "action", "symbol", "duration"].includes(col.key)
900
+ * );
901
+ * await service.getReport("BTCUSDT", "my-strategy", customColumns);
902
+ * ```
903
+ *
904
+ * @see {@link ScheduleMarkdownService} for usage in report generation
905
+ * @see {@link ColumnModel} for column interface definition
906
+ * @see {@link ScheduledEvent} for data structure
907
+ */
908
+ const schedule_columns = [
909
+ {
910
+ key: "timestamp",
911
+ label: "Timestamp",
912
+ format: (data) => new Date(data.timestamp).toISOString(),
913
+ isVisible: () => true,
914
+ },
915
+ {
916
+ key: "action",
917
+ label: "Action",
918
+ format: (data) => data.action.toUpperCase(),
919
+ isVisible: () => true,
920
+ },
921
+ {
922
+ key: "symbol",
923
+ label: "Symbol",
924
+ format: (data) => data.symbol,
925
+ isVisible: () => true,
926
+ },
927
+ {
928
+ key: "signalId",
929
+ label: "Signal ID",
930
+ format: (data) => data.signalId,
931
+ isVisible: () => true,
932
+ },
933
+ {
934
+ key: "position",
935
+ label: "Position",
936
+ format: (data) => data.position.toUpperCase(),
937
+ isVisible: () => true,
938
+ },
939
+ {
940
+ key: "note",
941
+ label: "Note",
942
+ format: (data) => toPlainString(data.note ?? "N/A"),
943
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
944
+ },
945
+ {
946
+ key: "currentPrice",
947
+ label: "Current Price",
948
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
949
+ isVisible: () => true,
950
+ },
951
+ {
952
+ key: "priceOpen",
953
+ label: "Entry Price",
954
+ format: (data) => `${data.priceOpen.toFixed(8)} USD`,
955
+ isVisible: () => true,
956
+ },
957
+ {
958
+ key: "takeProfit",
959
+ label: "Take Profit",
960
+ format: (data) => `${data.takeProfit.toFixed(8)} USD`,
961
+ isVisible: () => true,
962
+ },
963
+ {
964
+ key: "stopLoss",
965
+ label: "Stop Loss",
966
+ format: (data) => `${data.stopLoss.toFixed(8)} USD`,
967
+ isVisible: () => true,
968
+ },
969
+ {
970
+ key: "duration",
971
+ label: "Wait Time (min)",
972
+ format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
973
+ isVisible: () => true,
974
+ },
975
+ ];
976
+
977
+ /**
978
+ * Column configuration for walker strategy comparison table in markdown reports.
979
+ *
980
+ * Defines the table structure for displaying strategy comparison results in walker backtest reports.
981
+ * Each column specifies how to format and display aggregated strategy performance metrics.
982
+ *
983
+ * Used by {@link WalkerMarkdownService} to generate markdown tables showing:
984
+ * - Strategy ranking and identification
985
+ * - Optimization metric value (generic "Metric" column)
986
+ * - Performance statistics (Total Signals, Win Rate, Average PNL, Total PNL)
987
+ * - Risk metrics (Sharpe Ratio, Standard Deviation)
988
+ *
989
+ * @remarks
990
+ * This configuration is used in walker reports to compare multiple strategy configurations.
991
+ * The "Metric" column displays the value of the metric being optimized (Sharpe, PNL, etc.).
992
+ * Strategies are automatically sorted by metric value (best performers first).
993
+ *
994
+ * @example
995
+ * ```typescript
996
+ * import { walker_strategy_columns } from "./assets/walker.columns";
997
+ *
998
+ * // Use with WalkerMarkdownService for strategy comparison
999
+ * const service = new WalkerMarkdownService();
1000
+ * await service.getReport(
1001
+ * "my-walker",
1002
+ * "BTCUSDT",
1003
+ * "sharpeRatio",
1004
+ * { exchangeName: "binance", frameName: "1d" },
1005
+ * walker_strategy_columns
1006
+ * );
1007
+ * ```
1008
+ *
1009
+ * @see {@link WalkerMarkdownService} for usage in report generation
1010
+ * @see {@link ColumnModel} for column interface definition
1011
+ * @see {@link IStrategyResult} for data structure
1012
+ */
1013
+ const walker_strategy_columns = [
1014
+ {
1015
+ key: "rank",
1016
+ label: "Rank",
1017
+ format: (_data, index) => `${index + 1}`,
1018
+ isVisible: () => true,
1019
+ },
1020
+ {
1021
+ key: "strategy",
1022
+ label: "Strategy",
1023
+ format: (data) => data.strategyName,
1024
+ isVisible: () => true,
1025
+ },
1026
+ {
1027
+ key: "metric",
1028
+ label: "Metric",
1029
+ format: (data) => data.metricValue !== null ? data.metricValue.toFixed(2) : "N/A",
1030
+ isVisible: () => true,
1031
+ },
1032
+ {
1033
+ key: "totalSignals",
1034
+ label: "Total Signals",
1035
+ format: (data) => `${data.stats.totalSignals}`,
1036
+ isVisible: () => true,
1037
+ },
1038
+ {
1039
+ key: "winRate",
1040
+ label: "Win Rate",
1041
+ format: (data) => data.stats.winRate !== null
1042
+ ? `${data.stats.winRate.toFixed(2)}%`
1043
+ : "N/A",
1044
+ isVisible: () => true,
1045
+ },
1046
+ {
1047
+ key: "avgPnl",
1048
+ label: "Avg PNL",
1049
+ format: (data) => data.stats.avgPnl !== null
1050
+ ? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
1051
+ : "N/A",
1052
+ isVisible: () => true,
1053
+ },
1054
+ {
1055
+ key: "totalPnl",
1056
+ label: "Total PNL",
1057
+ format: (data) => data.stats.totalPnl !== null
1058
+ ? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
1059
+ : "N/A",
1060
+ isVisible: () => true,
1061
+ },
1062
+ {
1063
+ key: "sharpeRatio",
1064
+ label: "Sharpe Ratio",
1065
+ format: (data) => data.stats.sharpeRatio !== null
1066
+ ? `${data.stats.sharpeRatio.toFixed(3)}`
1067
+ : "N/A",
1068
+ isVisible: () => true,
1069
+ },
1070
+ {
1071
+ key: "stdDev",
1072
+ label: "Std Dev",
1073
+ format: (data) => data.stats.stdDev !== null
1074
+ ? `${data.stats.stdDev.toFixed(3)}%`
1075
+ : "N/A",
1076
+ isVisible: () => true,
1077
+ },
1078
+ ];
1079
+ /**
1080
+ * Column configuration for walker PNL table in markdown reports.
1081
+ *
1082
+ * Defines the table structure for displaying all closed signals across all strategies in walker backtest reports.
1083
+ * Each column specifies how to format and display individual signal trade data.
1084
+ *
1085
+ * Used by {@link WalkerMarkdownService} to generate markdown tables showing:
1086
+ * - Strategy identification for each signal
1087
+ * - Signal details (signal ID, symbol, position)
1088
+ * - Trade performance (PNL percentage, close reason)
1089
+ * - Timing information (open time, close time)
1090
+ *
1091
+ * @remarks
1092
+ * This configuration aggregates all signals from all tested strategies into a single comprehensive table.
1093
+ * Useful for detailed analysis of individual trades across different strategy configurations.
1094
+ *
1095
+ * @example
1096
+ * ```typescript
1097
+ * import { walker_pnl_columns } from "./assets/walker.columns";
1098
+ *
1099
+ * // Use with WalkerMarkdownService for signal-level analysis
1100
+ * const service = new WalkerMarkdownService();
1101
+ * await service.getReport(
1102
+ * "my-walker",
1103
+ * "BTCUSDT",
1104
+ * "sharpeRatio",
1105
+ * { exchangeName: "binance", frameName: "1d" },
1106
+ * undefined, // use default strategy columns
1107
+ * walker_pnl_columns
1108
+ * );
1109
+ * ```
1110
+ *
1111
+ * @see {@link WalkerMarkdownService} for usage in report generation
1112
+ * @see {@link ColumnModel} for column interface definition
1113
+ * @see {@link SignalData} for data structure
1114
+ */
1115
+ const walker_pnl_columns = [
1116
+ {
1117
+ key: "strategy",
1118
+ label: "Strategy",
1119
+ format: (data) => data.strategyName,
1120
+ isVisible: () => true,
1121
+ },
1122
+ {
1123
+ key: "signalId",
1124
+ label: "Signal ID",
1125
+ format: (data) => data.signalId,
1126
+ isVisible: () => true,
1127
+ },
1128
+ {
1129
+ key: "symbol",
1130
+ label: "Symbol",
1131
+ format: (data) => data.symbol,
1132
+ isVisible: () => true,
1133
+ },
1134
+ {
1135
+ key: "position",
1136
+ label: "Position",
1137
+ format: (data) => data.position.toUpperCase(),
1138
+ isVisible: () => true,
1139
+ },
1140
+ {
1141
+ key: "pnl",
1142
+ label: "PNL (net)",
1143
+ format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
1144
+ isVisible: () => true,
1145
+ },
1146
+ {
1147
+ key: "closeReason",
1148
+ label: "Close Reason",
1149
+ format: (data) => data.closeReason,
1150
+ isVisible: () => true,
1151
+ },
1152
+ {
1153
+ key: "openTime",
1154
+ label: "Open Time",
1155
+ format: (data) => new Date(data.openTime).toISOString(),
1156
+ isVisible: () => true,
1157
+ },
1158
+ {
1159
+ key: "closeTime",
1160
+ label: "Close Time",
1161
+ format: (data) => new Date(data.closeTime).toISOString(),
1162
+ isVisible: () => true,
1163
+ },
1164
+ ];
1165
+
1166
+ /**
1167
+ * Mapping of available table/markdown reports to their column definitions.
1168
+ *
1169
+ * Each property references a column definition object imported from
1170
+ * `src/assets/*.columns`. These are used by markdown/report generators
1171
+ * (backtest, live, schedule, risk, heat, performance, partial, walker).
1172
+ */
1173
+ const COLUMN_CONFIG = {
1174
+ /** Columns used in backtest markdown tables and reports */
1175
+ backtest_columns,
1176
+ /** Columns used by heatmap / heat reports */
1177
+ heat_columns,
1178
+ /** Columns for live trading reports and logs */
1179
+ live_columns,
1180
+ /** Columns for partial-results / incremental reports */
1181
+ partial_columns,
1182
+ /** Columns for performance summary reports */
1183
+ performance_columns,
1184
+ /** Columns for risk-related reports */
1185
+ risk_columns,
1186
+ /** Columns for scheduled report output */
1187
+ schedule_columns,
1188
+ /** Walker: PnL summary columns */
1189
+ walker_pnl_columns,
1190
+ /** Walker: strategy-level summary columns */
1191
+ walker_strategy_columns,
1192
+ };
1193
+ /**
1194
+ * Immutable default columns mapping used across the application.
1195
+ * Use `DEFAULT_COLUMNS` when you need a read-only reference to the
1196
+ * canonical column configuration.
1197
+ */
1198
+ const DEFAULT_COLUMNS = Object.freeze({ ...COLUMN_CONFIG });
1199
+
125
1200
  const { init, inject, provide } = diKit.createActivator("backtest");
126
1201
 
127
1202
  /**
@@ -221,6 +1296,7 @@ const validationServices$1 = {
221
1296
  riskValidationService: Symbol('riskValidationService'),
222
1297
  optimizerValidationService: Symbol('optimizerValidationService'),
223
1298
  configValidationService: Symbol('configValidationService'),
1299
+ columnValidationService: Symbol('columnValidationService'),
224
1300
  };
225
1301
  const templateServices$1 = {
226
1302
  optimizerTemplateService: Symbol('optimizerTemplateService'),
@@ -1765,53 +2841,6 @@ var emitters = /*#__PURE__*/Object.freeze({
1765
2841
  walkerStopSubject: walkerStopSubject
1766
2842
  });
1767
2843
 
1768
- /**
1769
- * Converts markdown content to plain text with minimal formatting
1770
- * @param content - Markdown string to convert
1771
- * @returns Plain text representation
1772
- */
1773
- const toPlainString = (content) => {
1774
- if (!content) {
1775
- return "";
1776
- }
1777
- let text = content;
1778
- // Remove code blocks
1779
- text = text.replace(/```[\s\S]*?```/g, "");
1780
- text = text.replace(/`([^`]+)`/g, "$1");
1781
- // Remove images
1782
- text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
1783
- // Convert links to text only (keep link text, remove URL)
1784
- text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
1785
- // Remove headers (convert to plain text)
1786
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
1787
- // Remove bold and italic markers
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
- text = text.replace(/__(.+?)__/g, "$1");
1793
- text = text.replace(/_(.+?)_/g, "$1");
1794
- // Remove strikethrough
1795
- text = text.replace(/~~(.+?)~~/g, "$1");
1796
- // Convert lists to plain text with bullets
1797
- text = text.replace(/^\s*[-*+]\s+/gm, "• ");
1798
- text = text.replace(/^\s*\d+\.\s+/gm, "• ");
1799
- // Remove blockquotes
1800
- text = text.replace(/^\s*>\s+/gm, "");
1801
- // Remove horizontal rules
1802
- text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
1803
- // Remove HTML tags
1804
- text = text.replace(/<[^>]+>/g, "");
1805
- // Remove excessive whitespace and normalize line breaks
1806
- text = text.replace(/\n[\s\n]*\n/g, "\n");
1807
- text = text.replace(/[ \t]+/g, " ");
1808
- // Remove all newline characters
1809
- text = text.replace(/\n/g, " ");
1810
- // Remove excessive spaces after newline removal
1811
- text = text.replace(/\s+/g, " ");
1812
- return text.trim();
1813
- };
1814
-
1815
2844
  const INTERVAL_MINUTES$1 = {
1816
2845
  "1m": 1,
1817
2846
  "3m": 3,
@@ -3390,7 +4419,7 @@ class RiskUtils {
3390
4419
  *
3391
4420
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3392
4421
  * @param strategyName - Strategy name (e.g., "my-strategy")
3393
- * @returns Promise resolving to RiskStatistics object with counts and event list
4422
+ * @returns Promise resolving to RiskStatisticsModel object with counts and event list
3394
4423
  *
3395
4424
  * @example
3396
4425
  * ```typescript
@@ -3437,6 +4466,7 @@ class RiskUtils {
3437
4466
  *
3438
4467
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3439
4468
  * @param strategyName - Strategy name (e.g., "my-strategy")
4469
+ * @param columns - Optional columns configuration for the report
3440
4470
  * @returns Promise resolving to markdown formatted report string
3441
4471
  *
3442
4472
  * @example
@@ -3460,7 +4490,7 @@ class RiskUtils {
3460
4490
  * // - my-strategy: 1
3461
4491
  * ```
3462
4492
  */
3463
- this.getReport = async (symbol, strategyName) => {
4493
+ this.getReport = async (symbol, strategyName, columns) => {
3464
4494
  backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, {
3465
4495
  symbol,
3466
4496
  strategyName,
@@ -3472,7 +4502,7 @@ class RiskUtils {
3472
4502
  backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
3473
4503
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT));
3474
4504
  }
3475
- return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
4505
+ return await backtest$1.riskMarkdownService.getReport(symbol, strategyName, columns);
3476
4506
  };
3477
4507
  /**
3478
4508
  * Generates and saves markdown report to file.
@@ -3489,6 +4519,7 @@ class RiskUtils {
3489
4519
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3490
4520
  * @param strategyName - Strategy name (e.g., "my-strategy")
3491
4521
  * @param path - Output directory path (default: "./dump/risk")
4522
+ * @param columns - Optional columns configuration for the report
3492
4523
  * @returns Promise that resolves when file is written
3493
4524
  *
3494
4525
  * @example
@@ -3505,7 +4536,7 @@ class RiskUtils {
3505
4536
  * }
3506
4537
  * ```
3507
4538
  */
3508
- this.dump = async (symbol, strategyName, path) => {
4539
+ this.dump = async (symbol, strategyName, path, columns) => {
3509
4540
  backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, {
3510
4541
  symbol,
3511
4542
  strategyName,
@@ -3518,7 +4549,7 @@ class RiskUtils {
3518
4549
  backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
3519
4550
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP));
3520
4551
  }
3521
- await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
4552
+ await backtest$1.riskMarkdownService.dump(symbol, strategyName, path, columns);
3522
4553
  };
3523
4554
  }
3524
4555
  }
@@ -5035,6 +6066,19 @@ class StrategySchemaService {
5035
6066
  if (typeof strategySchema.strategyName !== "string") {
5036
6067
  throw new Error(`strategy schema validation failed: missing strategyName`);
5037
6068
  }
6069
+ if (strategySchema.riskName && typeof strategySchema.riskName !== "string") {
6070
+ throw new Error(`strategy schema validation failed: invalid riskName`);
6071
+ }
6072
+ if (strategySchema.riskList && !Array.isArray(strategySchema.riskList)) {
6073
+ throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} system=${strategySchema.riskList}`);
6074
+ }
6075
+ if (strategySchema.riskList &&
6076
+ strategySchema.riskList.length !== new Set(strategySchema.riskList).size) {
6077
+ throw new Error(`strategy schema validation failed: found duplicate riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
6078
+ }
6079
+ if (strategySchema.riskList?.some((value) => typeof value !== "string")) {
6080
+ throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
6081
+ }
5038
6082
  if (typeof strategySchema.interval !== "string") {
5039
6083
  throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
5040
6084
  }
@@ -6265,178 +7309,91 @@ class BacktestCommandService {
6265
7309
  * @returns Async generator yielding closed signals with PNL
6266
7310
  */
6267
7311
  this.run = (symbol, context) => {
6268
- this.loggerService.log(METHOD_NAME_RUN$1, {
6269
- symbol,
6270
- context,
6271
- });
6272
- {
6273
- this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
6274
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
6275
- this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
6276
- }
6277
- {
6278
- const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
6279
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
6280
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
6281
- }
6282
- return this.backtestLogicPublicService.run(symbol, context);
6283
- };
6284
- }
6285
- }
6286
-
6287
- const METHOD_NAME_RUN = "walkerCommandService run";
6288
- /**
6289
- * Global service providing access to walker functionality.
6290
- *
6291
- * Simple wrapper around WalkerLogicPublicService for dependency injection.
6292
- * Used by public API exports.
6293
- */
6294
- class WalkerCommandService {
6295
- constructor() {
6296
- this.loggerService = inject(TYPES.loggerService);
6297
- this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
6298
- this.walkerSchemaService = inject(TYPES.walkerSchemaService);
6299
- this.strategyValidationService = inject(TYPES.strategyValidationService);
6300
- this.exchangeValidationService = inject(TYPES.exchangeValidationService);
6301
- this.frameValidationService = inject(TYPES.frameValidationService);
6302
- this.walkerValidationService = inject(TYPES.walkerValidationService);
6303
- this.strategySchemaService = inject(TYPES.strategySchemaService);
6304
- this.riskValidationService = inject(TYPES.riskValidationService);
6305
- /**
6306
- * Runs walker comparison for a symbol with context propagation.
6307
- *
6308
- * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
6309
- * @param context - Walker context with strategies and metric
6310
- */
6311
- this.run = (symbol, context) => {
6312
- this.loggerService.log(METHOD_NAME_RUN, {
6313
- symbol,
6314
- context,
6315
- });
6316
- {
6317
- this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
6318
- this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
6319
- this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
6320
- }
6321
- {
6322
- const walkerSchema = this.walkerSchemaService.get(context.walkerName);
6323
- for (const strategyName of walkerSchema.strategies) {
6324
- const { riskName, riskList } = this.strategySchemaService.get(strategyName);
6325
- this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
6326
- riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
6327
- riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
6328
- }
6329
- }
6330
- return this.walkerLogicPublicService.run(symbol, context);
6331
- };
6332
- }
6333
- }
6334
-
6335
- /**
6336
- * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
6337
- *
6338
- * @param value - Value to check
6339
- * @returns true if value is unsafe, false otherwise
6340
- */
6341
- function isUnsafe$3(value) {
6342
- if (typeof value !== "number") {
6343
- return true;
6344
- }
6345
- if (isNaN(value)) {
6346
- return true;
6347
- }
6348
- if (!isFinite(value)) {
6349
- return true;
6350
- }
6351
- return false;
6352
- }
6353
- const columns$6 = [
6354
- {
6355
- key: "signalId",
6356
- label: "Signal ID",
6357
- format: (data) => data.signal.id,
6358
- isVisible: () => true,
6359
- },
6360
- {
6361
- key: "symbol",
6362
- label: "Symbol",
6363
- format: (data) => data.signal.symbol,
6364
- isVisible: () => true,
6365
- },
6366
- {
6367
- key: "position",
6368
- label: "Position",
6369
- format: (data) => data.signal.position.toUpperCase(),
6370
- isVisible: () => true,
6371
- },
6372
- {
6373
- key: "note",
6374
- label: "Note",
6375
- format: (data) => toPlainString(data.signal.note ?? "N/A"),
6376
- isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
6377
- },
6378
- {
6379
- key: "openPrice",
6380
- label: "Open Price",
6381
- format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
6382
- isVisible: () => true,
6383
- },
6384
- {
6385
- key: "closePrice",
6386
- label: "Close Price",
6387
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
6388
- isVisible: () => true,
6389
- },
6390
- {
6391
- key: "takeProfit",
6392
- label: "Take Profit",
6393
- format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
6394
- isVisible: () => true,
6395
- },
6396
- {
6397
- key: "stopLoss",
6398
- label: "Stop Loss",
6399
- format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
6400
- isVisible: () => true,
6401
- },
6402
- {
6403
- key: "pnl",
6404
- label: "PNL (net)",
6405
- format: (data) => {
6406
- const pnlPercentage = data.pnl.pnlPercentage;
6407
- return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
6408
- },
6409
- isVisible: () => true,
6410
- },
6411
- {
6412
- key: "closeReason",
6413
- label: "Close Reason",
6414
- format: (data) => data.closeReason,
6415
- isVisible: () => true,
6416
- },
6417
- {
6418
- key: "duration",
6419
- label: "Duration (min)",
6420
- format: (data) => {
6421
- const durationMs = data.closeTimestamp - data.signal.pendingAt;
6422
- const durationMin = Math.round(durationMs / 60000);
6423
- return `${durationMin}`;
6424
- },
6425
- isVisible: () => true,
6426
- },
6427
- {
6428
- key: "openTimestamp",
6429
- label: "Open Time",
6430
- format: (data) => new Date(data.signal.pendingAt).toISOString(),
6431
- isVisible: () => true,
6432
- },
6433
- {
6434
- key: "closeTimestamp",
6435
- label: "Close Time",
6436
- format: (data) => new Date(data.closeTimestamp).toISOString(),
6437
- isVisible: () => true,
6438
- },
6439
- ];
7312
+ this.loggerService.log(METHOD_NAME_RUN$1, {
7313
+ symbol,
7314
+ context,
7315
+ });
7316
+ {
7317
+ this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
7318
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
7319
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
7320
+ }
7321
+ {
7322
+ const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
7323
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
7324
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
7325
+ }
7326
+ return this.backtestLogicPublicService.run(symbol, context);
7327
+ };
7328
+ }
7329
+ }
7330
+
7331
+ const METHOD_NAME_RUN = "walkerCommandService run";
7332
+ /**
7333
+ * Global service providing access to walker functionality.
7334
+ *
7335
+ * Simple wrapper around WalkerLogicPublicService for dependency injection.
7336
+ * Used by public API exports.
7337
+ */
7338
+ class WalkerCommandService {
7339
+ constructor() {
7340
+ this.loggerService = inject(TYPES.loggerService);
7341
+ this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
7342
+ this.walkerSchemaService = inject(TYPES.walkerSchemaService);
7343
+ this.strategyValidationService = inject(TYPES.strategyValidationService);
7344
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
7345
+ this.frameValidationService = inject(TYPES.frameValidationService);
7346
+ this.walkerValidationService = inject(TYPES.walkerValidationService);
7347
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
7348
+ this.riskValidationService = inject(TYPES.riskValidationService);
7349
+ /**
7350
+ * Runs walker comparison for a symbol with context propagation.
7351
+ *
7352
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
7353
+ * @param context - Walker context with strategies and metric
7354
+ */
7355
+ this.run = (symbol, context) => {
7356
+ this.loggerService.log(METHOD_NAME_RUN, {
7357
+ symbol,
7358
+ context,
7359
+ });
7360
+ {
7361
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
7362
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
7363
+ this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
7364
+ }
7365
+ {
7366
+ const walkerSchema = this.walkerSchemaService.get(context.walkerName);
7367
+ for (const strategyName of walkerSchema.strategies) {
7368
+ const { riskName, riskList } = this.strategySchemaService.get(strategyName);
7369
+ this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
7370
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
7371
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
7372
+ }
7373
+ }
7374
+ return this.walkerLogicPublicService.run(symbol, context);
7375
+ };
7376
+ }
7377
+ }
7378
+
7379
+ /**
7380
+ * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
7381
+ *
7382
+ * @param value - Value to check
7383
+ * @returns true if value is unsafe, false otherwise
7384
+ */
7385
+ function isUnsafe$3(value) {
7386
+ if (typeof value !== "number") {
7387
+ return true;
7388
+ }
7389
+ if (isNaN(value)) {
7390
+ return true;
7391
+ }
7392
+ if (!isFinite(value)) {
7393
+ return true;
7394
+ }
7395
+ return false;
7396
+ }
6440
7397
  /** Maximum number of signals to store in backtest reports */
6441
7398
  const MAX_EVENTS$6 = 250;
6442
7399
  /**
@@ -6530,9 +7487,10 @@ let ReportStorage$5 = class ReportStorage {
6530
7487
  * Generates markdown report with all closed signals for a strategy (View).
6531
7488
  *
6532
7489
  * @param strategyName - Strategy name
7490
+ * @param columns - Column configuration for formatting the table
6533
7491
  * @returns Markdown formatted report with all signals
6534
7492
  */
6535
- async getReport(strategyName) {
7493
+ async getReport(strategyName, columns = COLUMN_CONFIG.backtest_columns) {
6536
7494
  const stats = await this.getData();
6537
7495
  if (stats.totalSignals === 0) {
6538
7496
  return [
@@ -6541,10 +7499,15 @@ let ReportStorage$5 = class ReportStorage {
6541
7499
  "No signals closed yet."
6542
7500
  ].join("\n");
6543
7501
  }
6544
- const visibleColumns = columns$6.filter((col) => col.isVisible());
7502
+ const visibleColumns = [];
7503
+ for (const col of columns) {
7504
+ if (await col.isVisible()) {
7505
+ visibleColumns.push(col);
7506
+ }
7507
+ }
6545
7508
  const header = visibleColumns.map((col) => col.label);
6546
7509
  const separator = visibleColumns.map(() => "---");
6547
- const rows = this._signalList.map((closedSignal) => visibleColumns.map((col) => col.format(closedSignal)));
7510
+ const rows = await Promise.all(this._signalList.map(async (closedSignal, index) => Promise.all(visibleColumns.map((col) => col.format(closedSignal, index)))));
6548
7511
  const tableData = [header, separator, ...rows];
6549
7512
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6550
7513
  return [
@@ -6569,9 +7532,10 @@ let ReportStorage$5 = class ReportStorage {
6569
7532
  *
6570
7533
  * @param strategyName - Strategy name
6571
7534
  * @param path - Directory path to save report (default: "./dump/backtest")
7535
+ * @param columns - Column configuration for formatting the table
6572
7536
  */
6573
- async dump(strategyName, path$1 = "./dump/backtest") {
6574
- const markdown = await this.getReport(strategyName);
7537
+ async dump(strategyName, path$1 = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
7538
+ const markdown = await this.getReport(strategyName, columns);
6575
7539
  try {
6576
7540
  const dir = path.join(process.cwd(), path$1);
6577
7541
  await fs.mkdir(dir, { recursive: true });
@@ -6679,6 +7643,7 @@ class BacktestMarkdownService {
6679
7643
  *
6680
7644
  * @param symbol - Trading pair symbol
6681
7645
  * @param strategyName - Strategy name to generate report for
7646
+ * @param columns - Column configuration for formatting the table
6682
7647
  * @returns Markdown formatted report string with table of all closed signals
6683
7648
  *
6684
7649
  * @example
@@ -6688,13 +7653,13 @@ class BacktestMarkdownService {
6688
7653
  * console.log(markdown);
6689
7654
  * ```
6690
7655
  */
6691
- this.getReport = async (symbol, strategyName) => {
7656
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.backtest_columns) => {
6692
7657
  this.loggerService.log("backtestMarkdownService getReport", {
6693
7658
  symbol,
6694
7659
  strategyName,
6695
7660
  });
6696
7661
  const storage = this.getStorage(symbol, strategyName);
6697
- return storage.getReport(strategyName);
7662
+ return storage.getReport(strategyName, columns);
6698
7663
  };
6699
7664
  /**
6700
7665
  * Saves symbol-strategy report to disk.
@@ -6704,6 +7669,7 @@ class BacktestMarkdownService {
6704
7669
  * @param symbol - Trading pair symbol
6705
7670
  * @param strategyName - Strategy name to save report for
6706
7671
  * @param path - Directory path to save report (default: "./dump/backtest")
7672
+ * @param columns - Column configuration for formatting the table
6707
7673
  *
6708
7674
  * @example
6709
7675
  * ```typescript
@@ -6716,14 +7682,14 @@ class BacktestMarkdownService {
6716
7682
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
6717
7683
  * ```
6718
7684
  */
6719
- this.dump = async (symbol, strategyName, path = "./dump/backtest") => {
7685
+ this.dump = async (symbol, strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) => {
6720
7686
  this.loggerService.log("backtestMarkdownService dump", {
6721
7687
  symbol,
6722
7688
  strategyName,
6723
7689
  path,
6724
7690
  });
6725
7691
  const storage = this.getStorage(symbol, strategyName);
6726
- await storage.dump(strategyName, path);
7692
+ await storage.dump(strategyName, path, columns);
6727
7693
  };
6728
7694
  /**
6729
7695
  * Clears accumulated signal data from storage.
@@ -6791,104 +7757,6 @@ function isUnsafe$2(value) {
6791
7757
  }
6792
7758
  return false;
6793
7759
  }
6794
- const columns$5 = [
6795
- {
6796
- key: "timestamp",
6797
- label: "Timestamp",
6798
- format: (data) => new Date(data.timestamp).toISOString(),
6799
- isVisible: () => true,
6800
- },
6801
- {
6802
- key: "action",
6803
- label: "Action",
6804
- format: (data) => data.action.toUpperCase(),
6805
- isVisible: () => true,
6806
- },
6807
- {
6808
- key: "symbol",
6809
- label: "Symbol",
6810
- format: (data) => data.symbol ?? "N/A",
6811
- isVisible: () => true,
6812
- },
6813
- {
6814
- key: "signalId",
6815
- label: "Signal ID",
6816
- format: (data) => data.signalId ?? "N/A",
6817
- isVisible: () => true,
6818
- },
6819
- {
6820
- key: "position",
6821
- label: "Position",
6822
- format: (data) => data.position?.toUpperCase() ?? "N/A",
6823
- isVisible: () => true,
6824
- },
6825
- {
6826
- key: "note",
6827
- label: "Note",
6828
- format: (data) => toPlainString(data.note ?? "N/A"),
6829
- isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
6830
- },
6831
- {
6832
- key: "currentPrice",
6833
- label: "Current Price",
6834
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
6835
- isVisible: () => true,
6836
- },
6837
- {
6838
- key: "openPrice",
6839
- label: "Open Price",
6840
- format: (data) => data.openPrice !== undefined ? `${data.openPrice.toFixed(8)} USD` : "N/A",
6841
- isVisible: () => true,
6842
- },
6843
- {
6844
- key: "takeProfit",
6845
- label: "Take Profit",
6846
- format: (data) => data.takeProfit !== undefined
6847
- ? `${data.takeProfit.toFixed(8)} USD`
6848
- : "N/A",
6849
- isVisible: () => true,
6850
- },
6851
- {
6852
- key: "stopLoss",
6853
- label: "Stop Loss",
6854
- format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
6855
- isVisible: () => true,
6856
- },
6857
- {
6858
- key: "percentTp",
6859
- label: "% to TP",
6860
- format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
6861
- isVisible: () => true,
6862
- },
6863
- {
6864
- key: "percentSl",
6865
- label: "% to SL",
6866
- format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
6867
- isVisible: () => true,
6868
- },
6869
- {
6870
- key: "pnl",
6871
- label: "PNL (net)",
6872
- format: (data) => {
6873
- if (data.pnl === undefined)
6874
- return "N/A";
6875
- return `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`;
6876
- },
6877
- isVisible: () => true,
6878
- },
6879
- {
6880
- key: "closeReason",
6881
- label: "Close Reason",
6882
- format: (data) => data.closeReason ?? "N/A",
6883
- isVisible: () => true,
6884
- },
6885
- {
6886
- key: "duration",
6887
- label: "Duration (min)",
6888
- format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
6889
- isVisible: () => true,
6890
- },
6891
- ];
6892
7760
  /** Maximum number of events to store in live trading reports */
6893
7761
  const MAX_EVENTS$5 = 250;
6894
7762
  /**
@@ -7100,9 +7968,10 @@ let ReportStorage$4 = class ReportStorage {
7100
7968
  * Generates markdown report with all tick events for a strategy (View).
7101
7969
  *
7102
7970
  * @param strategyName - Strategy name
7971
+ * @param columns - Column configuration for formatting the table
7103
7972
  * @returns Markdown formatted report with all events
7104
7973
  */
7105
- async getReport(strategyName) {
7974
+ async getReport(strategyName, columns = COLUMN_CONFIG.live_columns) {
7106
7975
  const stats = await this.getData();
7107
7976
  if (stats.totalEvents === 0) {
7108
7977
  return [
@@ -7111,10 +7980,15 @@ let ReportStorage$4 = class ReportStorage {
7111
7980
  "No events recorded yet."
7112
7981
  ].join("\n");
7113
7982
  }
7114
- const visibleColumns = columns$5.filter((col) => col.isVisible());
7983
+ const visibleColumns = [];
7984
+ for (const col of columns) {
7985
+ if (await col.isVisible()) {
7986
+ visibleColumns.push(col);
7987
+ }
7988
+ }
7115
7989
  const header = visibleColumns.map((col) => col.label);
7116
7990
  const separator = visibleColumns.map(() => "---");
7117
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
7991
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
7118
7992
  const tableData = [header, separator, ...rows];
7119
7993
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
7120
7994
  return [
@@ -7139,9 +8013,10 @@ let ReportStorage$4 = class ReportStorage {
7139
8013
  *
7140
8014
  * @param strategyName - Strategy name
7141
8015
  * @param path - Directory path to save report (default: "./dump/live")
8016
+ * @param columns - Column configuration for formatting the table
7142
8017
  */
7143
- async dump(strategyName, path$1 = "./dump/live") {
7144
- const markdown = await this.getReport(strategyName);
8018
+ async dump(strategyName, path$1 = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
8019
+ const markdown = await this.getReport(strategyName, columns);
7145
8020
  try {
7146
8021
  const dir = path.join(process.cwd(), path$1);
7147
8022
  await fs.mkdir(dir, { recursive: true });
@@ -7262,6 +8137,7 @@ class LiveMarkdownService {
7262
8137
  *
7263
8138
  * @param symbol - Trading pair symbol
7264
8139
  * @param strategyName - Strategy name to generate report for
8140
+ * @param columns - Column configuration for formatting the table
7265
8141
  * @returns Markdown formatted report string with table of all events
7266
8142
  *
7267
8143
  * @example
@@ -7271,13 +8147,13 @@ class LiveMarkdownService {
7271
8147
  * console.log(markdown);
7272
8148
  * ```
7273
8149
  */
7274
- this.getReport = async (symbol, strategyName) => {
8150
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.live_columns) => {
7275
8151
  this.loggerService.log("liveMarkdownService getReport", {
7276
8152
  symbol,
7277
8153
  strategyName,
7278
8154
  });
7279
8155
  const storage = this.getStorage(symbol, strategyName);
7280
- return storage.getReport(strategyName);
8156
+ return storage.getReport(strategyName, columns);
7281
8157
  };
7282
8158
  /**
7283
8159
  * Saves symbol-strategy report to disk.
@@ -7287,6 +8163,7 @@ class LiveMarkdownService {
7287
8163
  * @param symbol - Trading pair symbol
7288
8164
  * @param strategyName - Strategy name to save report for
7289
8165
  * @param path - Directory path to save report (default: "./dump/live")
8166
+ * @param columns - Column configuration for formatting the table
7290
8167
  *
7291
8168
  * @example
7292
8169
  * ```typescript
@@ -7299,14 +8176,14 @@ class LiveMarkdownService {
7299
8176
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
7300
8177
  * ```
7301
8178
  */
7302
- this.dump = async (symbol, strategyName, path = "./dump/live") => {
8179
+ this.dump = async (symbol, strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) => {
7303
8180
  this.loggerService.log("liveMarkdownService dump", {
7304
8181
  symbol,
7305
8182
  strategyName,
7306
8183
  path,
7307
8184
  });
7308
8185
  const storage = this.getStorage(symbol, strategyName);
7309
- await storage.dump(strategyName, path);
8186
+ await storage.dump(strategyName, path, columns);
7310
8187
  };
7311
8188
  /**
7312
8189
  * Clears accumulated event data from storage.
@@ -7353,77 +8230,9 @@ class LiveMarkdownService {
7353
8230
  this.loggerService.log("liveMarkdownService init");
7354
8231
  signalLiveEmitter.subscribe(this.tick);
7355
8232
  });
7356
- }
7357
- }
7358
-
7359
- const columns$4 = [
7360
- {
7361
- key: "timestamp",
7362
- label: "Timestamp",
7363
- format: (data) => new Date(data.timestamp).toISOString(),
7364
- isVisible: () => true,
7365
- },
7366
- {
7367
- key: "action",
7368
- label: "Action",
7369
- format: (data) => data.action.toUpperCase(),
7370
- isVisible: () => true,
7371
- },
7372
- {
7373
- key: "symbol",
7374
- label: "Symbol",
7375
- format: (data) => data.symbol,
7376
- isVisible: () => true,
7377
- },
7378
- {
7379
- key: "signalId",
7380
- label: "Signal ID",
7381
- format: (data) => data.signalId,
7382
- isVisible: () => true,
7383
- },
7384
- {
7385
- key: "position",
7386
- label: "Position",
7387
- format: (data) => data.position.toUpperCase(),
7388
- isVisible: () => true,
7389
- },
7390
- {
7391
- key: "note",
7392
- label: "Note",
7393
- format: (data) => toPlainString(data.note ?? "N/A"),
7394
- isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
7395
- },
7396
- {
7397
- key: "currentPrice",
7398
- label: "Current Price",
7399
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
7400
- isVisible: () => true,
7401
- },
7402
- {
7403
- key: "priceOpen",
7404
- label: "Entry Price",
7405
- format: (data) => `${data.priceOpen.toFixed(8)} USD`,
7406
- isVisible: () => true,
7407
- },
7408
- {
7409
- key: "takeProfit",
7410
- label: "Take Profit",
7411
- format: (data) => `${data.takeProfit.toFixed(8)} USD`,
7412
- isVisible: () => true,
7413
- },
7414
- {
7415
- key: "stopLoss",
7416
- label: "Stop Loss",
7417
- format: (data) => `${data.stopLoss.toFixed(8)} USD`,
7418
- isVisible: () => true,
7419
- },
7420
- {
7421
- key: "duration",
7422
- label: "Wait Time (min)",
7423
- format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
7424
- isVisible: () => true,
7425
- },
7426
- ];
8233
+ }
8234
+ }
8235
+
7427
8236
  /** Maximum number of events to store in schedule reports */
7428
8237
  const MAX_EVENTS$4 = 250;
7429
8238
  /**
@@ -7568,9 +8377,10 @@ let ReportStorage$3 = class ReportStorage {
7568
8377
  * Generates markdown report with all scheduled events for a strategy (View).
7569
8378
  *
7570
8379
  * @param strategyName - Strategy name
8380
+ * @param columns - Column configuration for formatting the table
7571
8381
  * @returns Markdown formatted report with all events
7572
8382
  */
7573
- async getReport(strategyName) {
8383
+ async getReport(strategyName, columns = COLUMN_CONFIG.schedule_columns) {
7574
8384
  const stats = await this.getData();
7575
8385
  if (stats.totalEvents === 0) {
7576
8386
  return [
@@ -7579,10 +8389,15 @@ let ReportStorage$3 = class ReportStorage {
7579
8389
  "No scheduled signals recorded yet."
7580
8390
  ].join("\n");
7581
8391
  }
7582
- const visibleColumns = columns$4.filter((col) => col.isVisible());
8392
+ const visibleColumns = [];
8393
+ for (const col of columns) {
8394
+ if (await col.isVisible()) {
8395
+ visibleColumns.push(col);
8396
+ }
8397
+ }
7583
8398
  const header = visibleColumns.map((col) => col.label);
7584
8399
  const separator = visibleColumns.map(() => "---");
7585
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
8400
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
7586
8401
  const tableData = [header, separator, ...rows];
7587
8402
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7588
8403
  return [
@@ -7605,9 +8420,10 @@ let ReportStorage$3 = class ReportStorage {
7605
8420
  *
7606
8421
  * @param strategyName - Strategy name
7607
8422
  * @param path - Directory path to save report (default: "./dump/schedule")
8423
+ * @param columns - Column configuration for formatting the table
7608
8424
  */
7609
- async dump(strategyName, path$1 = "./dump/schedule") {
7610
- const markdown = await this.getReport(strategyName);
8425
+ async dump(strategyName, path$1 = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
8426
+ const markdown = await this.getReport(strategyName, columns);
7611
8427
  try {
7612
8428
  const dir = path.join(process.cwd(), path$1);
7613
8429
  await fs.mkdir(dir, { recursive: true });
@@ -7713,6 +8529,7 @@ class ScheduleMarkdownService {
7713
8529
  *
7714
8530
  * @param symbol - Trading pair symbol
7715
8531
  * @param strategyName - Strategy name to generate report for
8532
+ * @param columns - Column configuration for formatting the table
7716
8533
  * @returns Markdown formatted report string with table of all events
7717
8534
  *
7718
8535
  * @example
@@ -7722,13 +8539,13 @@ class ScheduleMarkdownService {
7722
8539
  * console.log(markdown);
7723
8540
  * ```
7724
8541
  */
7725
- this.getReport = async (symbol, strategyName) => {
8542
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.schedule_columns) => {
7726
8543
  this.loggerService.log("scheduleMarkdownService getReport", {
7727
8544
  symbol,
7728
8545
  strategyName,
7729
8546
  });
7730
8547
  const storage = this.getStorage(symbol, strategyName);
7731
- return storage.getReport(strategyName);
8548
+ return storage.getReport(strategyName, columns);
7732
8549
  };
7733
8550
  /**
7734
8551
  * Saves symbol-strategy report to disk.
@@ -7738,6 +8555,7 @@ class ScheduleMarkdownService {
7738
8555
  * @param symbol - Trading pair symbol
7739
8556
  * @param strategyName - Strategy name to save report for
7740
8557
  * @param path - Directory path to save report (default: "./dump/schedule")
8558
+ * @param columns - Column configuration for formatting the table
7741
8559
  *
7742
8560
  * @example
7743
8561
  * ```typescript
@@ -7750,14 +8568,14 @@ class ScheduleMarkdownService {
7750
8568
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
7751
8569
  * ```
7752
8570
  */
7753
- this.dump = async (symbol, strategyName, path = "./dump/schedule") => {
8571
+ this.dump = async (symbol, strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) => {
7754
8572
  this.loggerService.log("scheduleMarkdownService dump", {
7755
8573
  symbol,
7756
8574
  strategyName,
7757
8575
  path,
7758
8576
  });
7759
8577
  const storage = this.getStorage(symbol, strategyName);
7760
- await storage.dump(strategyName, path);
8578
+ await storage.dump(strategyName, path, columns);
7761
8579
  };
7762
8580
  /**
7763
8581
  * Clears accumulated event data from storage.
@@ -7816,86 +8634,6 @@ function percentile(sortedArray, p) {
7816
8634
  const index = Math.ceil((sortedArray.length * p) / 100) - 1;
7817
8635
  return sortedArray[Math.max(0, index)];
7818
8636
  }
7819
- const columns$3 = [
7820
- {
7821
- key: "metricType",
7822
- label: "Metric Type",
7823
- format: (data) => data.metricType,
7824
- isVisible: () => true,
7825
- },
7826
- {
7827
- key: "count",
7828
- label: "Count",
7829
- format: (data) => data.count.toString(),
7830
- isVisible: () => true,
7831
- },
7832
- {
7833
- key: "totalDuration",
7834
- label: "Total (ms)",
7835
- format: (data) => data.totalDuration.toFixed(2),
7836
- isVisible: () => true,
7837
- },
7838
- {
7839
- key: "avgDuration",
7840
- label: "Avg (ms)",
7841
- format: (data) => data.avgDuration.toFixed(2),
7842
- isVisible: () => true,
7843
- },
7844
- {
7845
- key: "minDuration",
7846
- label: "Min (ms)",
7847
- format: (data) => data.minDuration.toFixed(2),
7848
- isVisible: () => true,
7849
- },
7850
- {
7851
- key: "maxDuration",
7852
- label: "Max (ms)",
7853
- format: (data) => data.maxDuration.toFixed(2),
7854
- isVisible: () => true,
7855
- },
7856
- {
7857
- key: "stdDev",
7858
- label: "Std Dev (ms)",
7859
- format: (data) => data.stdDev.toFixed(2),
7860
- isVisible: () => true,
7861
- },
7862
- {
7863
- key: "median",
7864
- label: "Median (ms)",
7865
- format: (data) => data.median.toFixed(2),
7866
- isVisible: () => true,
7867
- },
7868
- {
7869
- key: "p95",
7870
- label: "P95 (ms)",
7871
- format: (data) => data.p95.toFixed(2),
7872
- isVisible: () => true,
7873
- },
7874
- {
7875
- key: "p99",
7876
- label: "P99 (ms)",
7877
- format: (data) => data.p99.toFixed(2),
7878
- isVisible: () => true,
7879
- },
7880
- {
7881
- key: "avgWaitTime",
7882
- label: "Avg Wait (ms)",
7883
- format: (data) => data.avgWaitTime.toFixed(2),
7884
- isVisible: () => true,
7885
- },
7886
- {
7887
- key: "minWaitTime",
7888
- label: "Min Wait (ms)",
7889
- format: (data) => data.minWaitTime.toFixed(2),
7890
- isVisible: () => true,
7891
- },
7892
- {
7893
- key: "maxWaitTime",
7894
- label: "Max Wait (ms)",
7895
- format: (data) => data.maxWaitTime.toFixed(2),
7896
- isVisible: () => true,
7897
- },
7898
- ];
7899
8637
  /** Maximum number of performance events to store per strategy */
7900
8638
  const MAX_EVENTS$3 = 10000;
7901
8639
  /**
@@ -7998,9 +8736,10 @@ class PerformanceStorage {
7998
8736
  * Generates markdown report with performance statistics.
7999
8737
  *
8000
8738
  * @param strategyName - Strategy name
8739
+ * @param columns - Column configuration for formatting the table
8001
8740
  * @returns Markdown formatted report
8002
8741
  */
8003
- async getReport(strategyName) {
8742
+ async getReport(strategyName, columns = COLUMN_CONFIG.performance_columns) {
8004
8743
  const stats = await this.getData(strategyName);
8005
8744
  if (stats.totalEvents === 0) {
8006
8745
  return [
@@ -8012,10 +8751,15 @@ class PerformanceStorage {
8012
8751
  // Sort metrics by total duration (descending) to show bottlenecks first
8013
8752
  const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
8014
8753
  // Generate summary table using Column interface
8015
- const visibleColumns = columns$3.filter((col) => col.isVisible());
8754
+ const visibleColumns = [];
8755
+ for (const col of columns) {
8756
+ if (await col.isVisible()) {
8757
+ visibleColumns.push(col);
8758
+ }
8759
+ }
8016
8760
  const header = visibleColumns.map((col) => col.label);
8017
8761
  const separator = visibleColumns.map(() => "---");
8018
- const rows = sortedMetrics.map((metric) => visibleColumns.map((col) => col.format(metric)));
8762
+ const rows = await Promise.all(sortedMetrics.map(async (metric, index) => Promise.all(visibleColumns.map((col) => col.format(metric, index)))));
8019
8763
  const tableData = [header, separator, ...rows];
8020
8764
  const summaryTable = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8021
8765
  // Calculate percentage of total time for each metric
@@ -8046,9 +8790,10 @@ class PerformanceStorage {
8046
8790
  *
8047
8791
  * @param strategyName - Strategy name
8048
8792
  * @param path - Directory path to save report
8793
+ * @param columns - Column configuration for formatting the table
8049
8794
  */
8050
- async dump(strategyName, path$1 = "./dump/performance") {
8051
- const markdown = await this.getReport(strategyName);
8795
+ async dump(strategyName, path$1 = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
8796
+ const markdown = await this.getReport(strategyName, columns);
8052
8797
  try {
8053
8798
  const dir = path.join(process.cwd(), path$1);
8054
8799
  await fs.mkdir(dir, { recursive: true });
@@ -8141,6 +8886,7 @@ class PerformanceMarkdownService {
8141
8886
  *
8142
8887
  * @param symbol - Trading pair symbol
8143
8888
  * @param strategyName - Strategy name to generate report for
8889
+ * @param columns - Column configuration for formatting the table
8144
8890
  * @returns Markdown formatted report string
8145
8891
  *
8146
8892
  * @example
@@ -8149,13 +8895,13 @@ class PerformanceMarkdownService {
8149
8895
  * console.log(markdown);
8150
8896
  * ```
8151
8897
  */
8152
- this.getReport = async (symbol, strategyName) => {
8898
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.performance_columns) => {
8153
8899
  this.loggerService.log("performanceMarkdownService getReport", {
8154
8900
  symbol,
8155
8901
  strategyName,
8156
8902
  });
8157
8903
  const storage = this.getStorage(symbol, strategyName);
8158
- return storage.getReport(strategyName);
8904
+ return storage.getReport(strategyName, columns);
8159
8905
  };
8160
8906
  /**
8161
8907
  * Saves performance report to disk.
@@ -8163,6 +8909,7 @@ class PerformanceMarkdownService {
8163
8909
  * @param symbol - Trading pair symbol
8164
8910
  * @param strategyName - Strategy name to save report for
8165
8911
  * @param path - Directory path to save report
8912
+ * @param columns - Column configuration for formatting the table
8166
8913
  *
8167
8914
  * @example
8168
8915
  * ```typescript
@@ -8173,14 +8920,14 @@ class PerformanceMarkdownService {
8173
8920
  * await performanceService.dump("BTCUSDT", "my-strategy", "./custom/path");
8174
8921
  * ```
8175
8922
  */
8176
- this.dump = async (symbol, strategyName, path = "./dump/performance") => {
8923
+ this.dump = async (symbol, strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) => {
8177
8924
  this.loggerService.log("performanceMarkdownService dump", {
8178
8925
  symbol,
8179
8926
  strategyName,
8180
8927
  path,
8181
8928
  });
8182
8929
  const storage = this.getStorage(symbol, strategyName);
8183
- await storage.dump(strategyName, path);
8930
+ await storage.dump(strategyName, path, columns);
8184
8931
  };
8185
8932
  /**
8186
8933
  * Clears accumulated performance data from storage.
@@ -8239,135 +8986,6 @@ function formatMetric(value) {
8239
8986
  }
8240
8987
  return value.toFixed(2);
8241
8988
  }
8242
- /**
8243
- * Creates strategy comparison columns based on metric name.
8244
- * Dynamically builds column configuration with metric-specific header.
8245
- *
8246
- * @param metric - Metric being optimized
8247
- * @returns Array of column configurations for strategy comparison table
8248
- */
8249
- function createStrategyColumns(metric) {
8250
- return [
8251
- {
8252
- key: "rank",
8253
- label: "Rank",
8254
- format: (data, index) => `${index + 1}`,
8255
- isVisible: () => true,
8256
- },
8257
- {
8258
- key: "strategy",
8259
- label: "Strategy",
8260
- format: (data) => data.strategyName,
8261
- isVisible: () => true,
8262
- },
8263
- {
8264
- key: "metric",
8265
- label: metric,
8266
- format: (data) => formatMetric(data.metricValue),
8267
- isVisible: () => true,
8268
- },
8269
- {
8270
- key: "totalSignals",
8271
- label: "Total Signals",
8272
- format: (data) => `${data.stats.totalSignals}`,
8273
- isVisible: () => true,
8274
- },
8275
- {
8276
- key: "winRate",
8277
- label: "Win Rate",
8278
- format: (data) => data.stats.winRate !== null
8279
- ? `${data.stats.winRate.toFixed(2)}%`
8280
- : "N/A",
8281
- isVisible: () => true,
8282
- },
8283
- {
8284
- key: "avgPnl",
8285
- label: "Avg PNL",
8286
- format: (data) => data.stats.avgPnl !== null
8287
- ? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
8288
- : "N/A",
8289
- isVisible: () => true,
8290
- },
8291
- {
8292
- key: "totalPnl",
8293
- label: "Total PNL",
8294
- format: (data) => data.stats.totalPnl !== null
8295
- ? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
8296
- : "N/A",
8297
- isVisible: () => true,
8298
- },
8299
- {
8300
- key: "sharpeRatio",
8301
- label: "Sharpe Ratio",
8302
- format: (data) => data.stats.sharpeRatio !== null
8303
- ? `${data.stats.sharpeRatio.toFixed(3)}`
8304
- : "N/A",
8305
- isVisible: () => true,
8306
- },
8307
- {
8308
- key: "stdDev",
8309
- label: "Std Dev",
8310
- format: (data) => data.stats.stdDev !== null
8311
- ? `${data.stats.stdDev.toFixed(3)}%`
8312
- : "N/A",
8313
- isVisible: () => true,
8314
- },
8315
- ];
8316
- }
8317
- /**
8318
- * Column configuration for PNL table.
8319
- * Defines all columns for displaying closed signals across strategies.
8320
- */
8321
- const pnlColumns = [
8322
- {
8323
- key: "strategy",
8324
- label: "Strategy",
8325
- format: (data) => data.strategyName,
8326
- isVisible: () => true,
8327
- },
8328
- {
8329
- key: "signalId",
8330
- label: "Signal ID",
8331
- format: (data) => data.signalId,
8332
- isVisible: () => true,
8333
- },
8334
- {
8335
- key: "symbol",
8336
- label: "Symbol",
8337
- format: (data) => data.symbol,
8338
- isVisible: () => true,
8339
- },
8340
- {
8341
- key: "position",
8342
- label: "Position",
8343
- format: (data) => data.position.toUpperCase(),
8344
- isVisible: () => true,
8345
- },
8346
- {
8347
- key: "pnl",
8348
- label: "PNL (net)",
8349
- format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
8350
- isVisible: () => true,
8351
- },
8352
- {
8353
- key: "closeReason",
8354
- label: "Close Reason",
8355
- format: (data) => data.closeReason,
8356
- isVisible: () => true,
8357
- },
8358
- {
8359
- key: "openTime",
8360
- label: "Open Time",
8361
- format: (data) => new Date(data.openTime).toISOString(),
8362
- isVisible: () => true,
8363
- },
8364
- {
8365
- key: "closeTime",
8366
- label: "Close Time",
8367
- format: (data) => new Date(data.closeTime).toISOString(),
8368
- isVisible: () => true,
8369
- },
8370
- ];
8371
8989
  /**
8372
8990
  * Storage class for accumulating walker results.
8373
8991
  * Maintains a list of all strategy results and provides methods to generate reports.
@@ -8399,7 +9017,7 @@ let ReportStorage$2 = class ReportStorage {
8399
9017
  this._strategyResults.unshift({
8400
9018
  strategyName: data.strategyName,
8401
9019
  stats: data.stats,
8402
- metricValue: data.metricValue,
9020
+ metricValue: isUnsafe$1(data.metricValue) ? null : data.metricValue,
8403
9021
  });
8404
9022
  }
8405
9023
  /**
@@ -8432,11 +9050,11 @@ let ReportStorage$2 = class ReportStorage {
8432
9050
  * Generates comparison table for top N strategies (View).
8433
9051
  * Sorts strategies by metric value and formats as markdown table.
8434
9052
  *
8435
- * @param metric - Metric being optimized
8436
9053
  * @param topN - Number of top strategies to include (default: 10)
9054
+ * @param columns - Column configuration for formatting the strategy comparison table
8437
9055
  * @returns Markdown formatted comparison table
8438
9056
  */
8439
- getComparisonTable(metric, topN = 10) {
9057
+ async getComparisonTable(topN = 10, columns = COLUMN_CONFIG.walker_strategy_columns) {
8440
9058
  if (this._strategyResults.length === 0) {
8441
9059
  return "No strategy results available.";
8442
9060
  }
@@ -8449,13 +9067,17 @@ let ReportStorage$2 = class ReportStorage {
8449
9067
  // Take top N strategies
8450
9068
  const topStrategies = sortedResults.slice(0, topN);
8451
9069
  // Get columns configuration
8452
- const columns = createStrategyColumns(metric);
8453
- const visibleColumns = columns.filter((col) => col.isVisible());
9070
+ const visibleColumns = [];
9071
+ for (const col of columns) {
9072
+ if (await col.isVisible()) {
9073
+ visibleColumns.push(col);
9074
+ }
9075
+ }
8454
9076
  // Build table header
8455
9077
  const header = visibleColumns.map((col) => col.label);
8456
9078
  const separator = visibleColumns.map(() => "---");
8457
9079
  // Build table rows
8458
- const rows = topStrategies.map((result, index) => visibleColumns.map((col) => col.format(result, index)));
9080
+ const rows = await Promise.all(topStrategies.map(async (result, index) => Promise.all(visibleColumns.map((col) => col.format(result, index)))));
8459
9081
  const tableData = [header, separator, ...rows];
8460
9082
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8461
9083
  }
@@ -8463,9 +9085,10 @@ let ReportStorage$2 = class ReportStorage {
8463
9085
  * Generates PNL table showing all closed signals across all strategies (View).
8464
9086
  * Collects all signals from all strategies and formats as markdown table.
8465
9087
  *
9088
+ * @param columns - Column configuration for formatting the PNL table
8466
9089
  * @returns Markdown formatted PNL table
8467
9090
  */
8468
- getPnlTable() {
9091
+ async getPnlTable(columns = COLUMN_CONFIG.walker_pnl_columns) {
8469
9092
  if (this._strategyResults.length === 0) {
8470
9093
  return "No strategy results available.";
8471
9094
  }
@@ -8489,11 +9112,16 @@ let ReportStorage$2 = class ReportStorage {
8489
9112
  return "No closed signals available.";
8490
9113
  }
8491
9114
  // Build table header
8492
- const visibleColumns = pnlColumns.filter((col) => col.isVisible());
9115
+ const visibleColumns = [];
9116
+ for (const col of columns) {
9117
+ if (await col.isVisible()) {
9118
+ visibleColumns.push(col);
9119
+ }
9120
+ }
8493
9121
  const header = visibleColumns.map((col) => col.label);
8494
9122
  const separator = visibleColumns.map(() => "---");
8495
9123
  // Build table rows
8496
- const rows = allSignals.map((signal) => visibleColumns.map((col) => col.format(signal)));
9124
+ const rows = await Promise.all(allSignals.map(async (signal, index) => Promise.all(visibleColumns.map((col) => col.format(signal, index)))));
8497
9125
  const tableData = [header, separator, ...rows];
8498
9126
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8499
9127
  }
@@ -8504,9 +9132,11 @@ let ReportStorage$2 = class ReportStorage {
8504
9132
  * @param symbol - Trading symbol
8505
9133
  * @param metric - Metric being optimized
8506
9134
  * @param context - Context with exchangeName and frameName
9135
+ * @param strategyColumns - Column configuration for strategy comparison table
9136
+ * @param pnlColumns - Column configuration for PNL table
8507
9137
  * @returns Markdown formatted report with all results
8508
9138
  */
8509
- async getReport(symbol, metric, context) {
9139
+ async getReport(symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
8510
9140
  const results = await this.getData(symbol, metric, context);
8511
9141
  // Get total signals for best strategy
8512
9142
  const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
@@ -8526,11 +9156,11 @@ let ReportStorage$2 = class ReportStorage {
8526
9156
  "",
8527
9157
  "## Top Strategies Comparison",
8528
9158
  "",
8529
- this.getComparisonTable(metric, 10),
9159
+ await this.getComparisonTable(10, strategyColumns),
8530
9160
  "",
8531
9161
  "## All Signals (PNL Table)",
8532
9162
  "",
8533
- this.getPnlTable(),
9163
+ await this.getPnlTable(pnlColumns),
8534
9164
  "",
8535
9165
  "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
8536
9166
  ].join("\n");
@@ -8542,9 +9172,11 @@ let ReportStorage$2 = class ReportStorage {
8542
9172
  * @param metric - Metric being optimized
8543
9173
  * @param context - Context with exchangeName and frameName
8544
9174
  * @param path - Directory path to save report (default: "./dump/walker")
9175
+ * @param strategyColumns - Column configuration for strategy comparison table
9176
+ * @param pnlColumns - Column configuration for PNL table
8545
9177
  */
8546
- async dump(symbol, metric, context, path$1 = "./dump/walker") {
8547
- const markdown = await this.getReport(symbol, metric, context);
9178
+ async dump(symbol, metric, context, path$1 = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
9179
+ const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
8548
9180
  try {
8549
9181
  const dir = path.join(process.cwd(), path$1);
8550
9182
  await fs.mkdir(dir, { recursive: true });
@@ -8637,6 +9269,8 @@ class WalkerMarkdownService {
8637
9269
  * @param symbol - Trading symbol
8638
9270
  * @param metric - Metric being optimized
8639
9271
  * @param context - Context with exchangeName and frameName
9272
+ * @param strategyColumns - Column configuration for strategy comparison table
9273
+ * @param pnlColumns - Column configuration for PNL table
8640
9274
  * @returns Markdown formatted report string
8641
9275
  *
8642
9276
  * @example
@@ -8646,7 +9280,7 @@ class WalkerMarkdownService {
8646
9280
  * console.log(markdown);
8647
9281
  * ```
8648
9282
  */
8649
- this.getReport = async (walkerName, symbol, metric, context) => {
9283
+ this.getReport = async (walkerName, symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
8650
9284
  this.loggerService.log("walkerMarkdownService getReport", {
8651
9285
  walkerName,
8652
9286
  symbol,
@@ -8654,7 +9288,7 @@ class WalkerMarkdownService {
8654
9288
  context,
8655
9289
  });
8656
9290
  const storage = this.getStorage(walkerName);
8657
- return storage.getReport(symbol, metric, context);
9291
+ return storage.getReport(symbol, metric, context, strategyColumns, pnlColumns);
8658
9292
  };
8659
9293
  /**
8660
9294
  * Saves walker report to disk.
@@ -8666,6 +9300,8 @@ class WalkerMarkdownService {
8666
9300
  * @param metric - Metric being optimized
8667
9301
  * @param context - Context with exchangeName and frameName
8668
9302
  * @param path - Directory path to save report (default: "./dump/walker")
9303
+ * @param strategyColumns - Column configuration for strategy comparison table
9304
+ * @param pnlColumns - Column configuration for PNL table
8669
9305
  *
8670
9306
  * @example
8671
9307
  * ```typescript
@@ -8678,7 +9314,7 @@ class WalkerMarkdownService {
8678
9314
  * await service.dump("my-walker", "BTCUSDT", "sharpeRatio", { exchangeName: "binance", frameName: "1d" }, "./custom/path");
8679
9315
  * ```
8680
9316
  */
8681
- this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker") => {
9317
+ this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
8682
9318
  this.loggerService.log("walkerMarkdownService dump", {
8683
9319
  walkerName,
8684
9320
  symbol,
@@ -8687,7 +9323,7 @@ class WalkerMarkdownService {
8687
9323
  path,
8688
9324
  });
8689
9325
  const storage = this.getStorage(walkerName);
8690
- await storage.dump(symbol, metric, context, path);
9326
+ await storage.dump(symbol, metric, context, path, strategyColumns, pnlColumns);
8691
9327
  };
8692
9328
  /**
8693
9329
  * Clears accumulated result data from storage.
@@ -8734,99 +9370,25 @@ class WalkerMarkdownService {
8734
9370
  const HEATMAP_METHOD_NAME_GET_DATA = "HeatMarkdownService.getData";
8735
9371
  const HEATMAP_METHOD_NAME_GET_REPORT = "HeatMarkdownService.getReport";
8736
9372
  const HEATMAP_METHOD_NAME_DUMP = "HeatMarkdownService.dump";
8737
- const HEATMAP_METHOD_NAME_CLEAR = "HeatMarkdownService.clear";
8738
- /**
8739
- * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
8740
- *
8741
- * @param value - Value to check
8742
- * @returns true if value is unsafe, false otherwise
8743
- */
8744
- function isUnsafe(value) {
8745
- if (typeof value !== "number") {
8746
- return true;
8747
- }
8748
- if (isNaN(value)) {
8749
- return true;
8750
- }
8751
- if (!isFinite(value)) {
8752
- return true;
8753
- }
8754
- return false;
8755
- }
8756
- const columns$2 = [
8757
- {
8758
- key: "symbol",
8759
- label: "Symbol",
8760
- format: (data) => data.symbol,
8761
- isVisible: () => true,
8762
- },
8763
- {
8764
- key: "totalPnl",
8765
- label: "Total PNL",
8766
- format: (data) => data.totalPnl !== null ? functoolsKit.str(data.totalPnl, "%+.2f%%") : "N/A",
8767
- isVisible: () => true,
8768
- },
8769
- {
8770
- key: "sharpeRatio",
8771
- label: "Sharpe",
8772
- format: (data) => data.sharpeRatio !== null ? functoolsKit.str(data.sharpeRatio, "%.2f") : "N/A",
8773
- isVisible: () => true,
8774
- },
8775
- {
8776
- key: "profitFactor",
8777
- label: "PF",
8778
- format: (data) => data.profitFactor !== null ? functoolsKit.str(data.profitFactor, "%.2f") : "N/A",
8779
- isVisible: () => true,
8780
- },
8781
- {
8782
- key: "expectancy",
8783
- label: "Expect",
8784
- format: (data) => data.expectancy !== null ? functoolsKit.str(data.expectancy, "%+.2f%%") : "N/A",
8785
- isVisible: () => true,
8786
- },
8787
- {
8788
- key: "winRate",
8789
- label: "WR",
8790
- format: (data) => data.winRate !== null ? functoolsKit.str(data.winRate, "%.1f%%") : "N/A",
8791
- isVisible: () => true,
8792
- },
8793
- {
8794
- key: "avgWin",
8795
- label: "Avg Win",
8796
- format: (data) => data.avgWin !== null ? functoolsKit.str(data.avgWin, "%+.2f%%") : "N/A",
8797
- isVisible: () => true,
8798
- },
8799
- {
8800
- key: "avgLoss",
8801
- label: "Avg Loss",
8802
- format: (data) => data.avgLoss !== null ? functoolsKit.str(data.avgLoss, "%+.2f%%") : "N/A",
8803
- isVisible: () => true,
8804
- },
8805
- {
8806
- key: "maxDrawdown",
8807
- label: "Max DD",
8808
- format: (data) => data.maxDrawdown !== null ? functoolsKit.str(-data.maxDrawdown, "%.2f%%") : "N/A",
8809
- isVisible: () => true,
8810
- },
8811
- {
8812
- key: "maxWinStreak",
8813
- label: "W Streak",
8814
- format: (data) => data.maxWinStreak.toString(),
8815
- isVisible: () => true,
8816
- },
8817
- {
8818
- key: "maxLossStreak",
8819
- label: "L Streak",
8820
- format: (data) => data.maxLossStreak.toString(),
8821
- isVisible: () => true,
8822
- },
8823
- {
8824
- key: "totalTrades",
8825
- label: "Trades",
8826
- format: (data) => data.totalTrades.toString(),
8827
- isVisible: () => true,
8828
- },
8829
- ];
9373
+ const HEATMAP_METHOD_NAME_CLEAR = "HeatMarkdownService.clear";
9374
+ /**
9375
+ * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
9376
+ *
9377
+ * @param value - Value to check
9378
+ * @returns true if value is unsafe, false otherwise
9379
+ */
9380
+ function isUnsafe(value) {
9381
+ if (typeof value !== "number") {
9382
+ return true;
9383
+ }
9384
+ if (isNaN(value)) {
9385
+ return true;
9386
+ }
9387
+ if (!isFinite(value)) {
9388
+ return true;
9389
+ }
9390
+ return false;
9391
+ }
8830
9392
  /** Maximum number of signals to store per symbol in heatmap reports */
8831
9393
  const MAX_EVENTS$2 = 250;
8832
9394
  /**
@@ -9061,9 +9623,10 @@ class HeatmapStorage {
9061
9623
  * Generates markdown report with portfolio heatmap table (View).
9062
9624
  *
9063
9625
  * @param strategyName - Strategy name for report title
9626
+ * @param columns - Column configuration for formatting the table
9064
9627
  * @returns Promise resolving to markdown formatted report string
9065
9628
  */
9066
- async getReport(strategyName) {
9629
+ async getReport(strategyName, columns = COLUMN_CONFIG.heat_columns) {
9067
9630
  const data = await this.getData();
9068
9631
  if (data.symbols.length === 0) {
9069
9632
  return [
@@ -9072,10 +9635,15 @@ class HeatmapStorage {
9072
9635
  "*No data available*"
9073
9636
  ].join("\n");
9074
9637
  }
9075
- const visibleColumns = columns$2.filter((col) => col.isVisible());
9638
+ const visibleColumns = [];
9639
+ for (const col of columns) {
9640
+ if (await col.isVisible()) {
9641
+ visibleColumns.push(col);
9642
+ }
9643
+ }
9076
9644
  const header = visibleColumns.map((col) => col.label);
9077
9645
  const separator = visibleColumns.map(() => "---");
9078
- const rows = data.symbols.map((row) => visibleColumns.map((col) => col.format(row)));
9646
+ const rows = await Promise.all(data.symbols.map(async (row, index) => Promise.all(visibleColumns.map((col) => col.format(row, index)))));
9079
9647
  const tableData = [header, separator, ...rows];
9080
9648
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
9081
9649
  return [
@@ -9091,9 +9659,10 @@ class HeatmapStorage {
9091
9659
  *
9092
9660
  * @param strategyName - Strategy name for filename
9093
9661
  * @param path - Directory path to save report (default: "./dump/heatmap")
9662
+ * @param columns - Column configuration for formatting the table
9094
9663
  */
9095
- async dump(strategyName, path$1 = "./dump/heatmap") {
9096
- const markdown = await this.getReport(strategyName);
9664
+ async dump(strategyName, path$1 = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
9665
+ const markdown = await this.getReport(strategyName, columns);
9097
9666
  try {
9098
9667
  const dir = path.join(process.cwd(), path$1);
9099
9668
  await fs.mkdir(dir, { recursive: true });
@@ -9190,6 +9759,7 @@ class HeatMarkdownService {
9190
9759
  * Generates markdown report with portfolio heatmap table for a strategy.
9191
9760
  *
9192
9761
  * @param strategyName - Strategy name to generate heatmap report for
9762
+ * @param columns - Column configuration for formatting the table
9193
9763
  * @returns Promise resolving to markdown formatted report string
9194
9764
  *
9195
9765
  * @example
@@ -9209,12 +9779,12 @@ class HeatMarkdownService {
9209
9779
  * // ...
9210
9780
  * ```
9211
9781
  */
9212
- this.getReport = async (strategyName) => {
9782
+ this.getReport = async (strategyName, columns = COLUMN_CONFIG.heat_columns) => {
9213
9783
  this.loggerService.log(HEATMAP_METHOD_NAME_GET_REPORT, {
9214
9784
  strategyName,
9215
9785
  });
9216
9786
  const storage = this.getStorage(strategyName);
9217
- return storage.getReport(strategyName);
9787
+ return storage.getReport(strategyName, columns);
9218
9788
  };
9219
9789
  /**
9220
9790
  * Saves heatmap report to disk for a strategy.
@@ -9224,6 +9794,7 @@ class HeatMarkdownService {
9224
9794
  *
9225
9795
  * @param strategyName - Strategy name to save heatmap report for
9226
9796
  * @param path - Optional directory path to save report (default: "./dump/heatmap")
9797
+ * @param columns - Column configuration for formatting the table
9227
9798
  *
9228
9799
  * @example
9229
9800
  * ```typescript
@@ -9236,13 +9807,13 @@ class HeatMarkdownService {
9236
9807
  * await service.dump("my-strategy", "./reports");
9237
9808
  * ```
9238
9809
  */
9239
- this.dump = async (strategyName, path = "./dump/heatmap") => {
9810
+ this.dump = async (strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) => {
9240
9811
  this.loggerService.log(HEATMAP_METHOD_NAME_DUMP, {
9241
9812
  strategyName,
9242
9813
  path,
9243
9814
  });
9244
9815
  const storage = this.getStorage(strategyName);
9245
- await storage.dump(strategyName, path);
9816
+ await storage.dump(strategyName, path, columns);
9246
9817
  };
9247
9818
  /**
9248
9819
  * Clears accumulated heatmap data from storage.
@@ -11633,62 +12204,6 @@ class PartialConnectionService {
11633
12204
  }
11634
12205
  }
11635
12206
 
11636
- const columns$1 = [
11637
- {
11638
- key: "action",
11639
- label: "Action",
11640
- format: (data) => data.action.toUpperCase(),
11641
- isVisible: () => true,
11642
- },
11643
- {
11644
- key: "symbol",
11645
- label: "Symbol",
11646
- format: (data) => data.symbol,
11647
- isVisible: () => true,
11648
- },
11649
- {
11650
- key: "strategyName",
11651
- label: "Strategy",
11652
- format: (data) => data.strategyName,
11653
- isVisible: () => true,
11654
- },
11655
- {
11656
- key: "signalId",
11657
- label: "Signal ID",
11658
- format: (data) => data.signalId,
11659
- isVisible: () => true,
11660
- },
11661
- {
11662
- key: "position",
11663
- label: "Position",
11664
- format: (data) => data.position.toUpperCase(),
11665
- isVisible: () => true,
11666
- },
11667
- {
11668
- key: "level",
11669
- label: "Level %",
11670
- format: (data) => data.action === "profit" ? `+${data.level}%` : `-${data.level}%`,
11671
- isVisible: () => true,
11672
- },
11673
- {
11674
- key: "currentPrice",
11675
- label: "Current Price",
11676
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
11677
- isVisible: () => true,
11678
- },
11679
- {
11680
- key: "timestamp",
11681
- label: "Timestamp",
11682
- format: (data) => new Date(data.timestamp).toISOString(),
11683
- isVisible: () => true,
11684
- },
11685
- {
11686
- key: "mode",
11687
- label: "Mode",
11688
- format: (data) => (data.backtest ? "Backtest" : "Live"),
11689
- isVisible: () => true,
11690
- },
11691
- ];
11692
12207
  /** Maximum number of events to store in partial reports */
11693
12208
  const MAX_EVENTS$1 = 250;
11694
12209
  /**
@@ -11778,9 +12293,10 @@ let ReportStorage$1 = class ReportStorage {
11778
12293
  *
11779
12294
  * @param symbol - Trading pair symbol
11780
12295
  * @param strategyName - Strategy name
12296
+ * @param columns - Column configuration for formatting the table
11781
12297
  * @returns Markdown formatted report with all events
11782
12298
  */
11783
- async getReport(symbol, strategyName) {
12299
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) {
11784
12300
  const stats = await this.getData();
11785
12301
  if (stats.totalEvents === 0) {
11786
12302
  return [
@@ -11789,10 +12305,15 @@ let ReportStorage$1 = class ReportStorage {
11789
12305
  "No partial profit/loss events recorded yet."
11790
12306
  ].join("\n");
11791
12307
  }
11792
- const visibleColumns = columns$1.filter((col) => col.isVisible());
12308
+ const visibleColumns = [];
12309
+ for (const col of columns) {
12310
+ if (await col.isVisible()) {
12311
+ visibleColumns.push(col);
12312
+ }
12313
+ }
11793
12314
  const header = visibleColumns.map((col) => col.label);
11794
12315
  const separator = visibleColumns.map(() => "---");
11795
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
12316
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
11796
12317
  const tableData = [header, separator, ...rows];
11797
12318
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
11798
12319
  return [
@@ -11811,9 +12332,10 @@ let ReportStorage$1 = class ReportStorage {
11811
12332
  * @param symbol - Trading pair symbol
11812
12333
  * @param strategyName - Strategy name
11813
12334
  * @param path - Directory path to save report (default: "./dump/partial")
12335
+ * @param columns - Column configuration for formatting the table
11814
12336
  */
11815
- async dump(symbol, strategyName, path$1 = "./dump/partial") {
11816
- const markdown = await this.getReport(symbol, strategyName);
12337
+ async dump(symbol, strategyName, path$1 = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
12338
+ const markdown = await this.getReport(symbol, strategyName, columns);
11817
12339
  try {
11818
12340
  const dir = path.join(process.cwd(), path$1);
11819
12341
  await fs.mkdir(dir, { recursive: true });
@@ -11924,6 +12446,7 @@ class PartialMarkdownService {
11924
12446
  *
11925
12447
  * @param symbol - Trading pair symbol to generate report for
11926
12448
  * @param strategyName - Strategy name to generate report for
12449
+ * @param columns - Column configuration for formatting the table
11927
12450
  * @returns Markdown formatted report string with table of all events
11928
12451
  *
11929
12452
  * @example
@@ -11933,13 +12456,13 @@ class PartialMarkdownService {
11933
12456
  * console.log(markdown);
11934
12457
  * ```
11935
12458
  */
11936
- this.getReport = async (symbol, strategyName) => {
12459
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) => {
11937
12460
  this.loggerService.log("partialMarkdownService getReport", {
11938
12461
  symbol,
11939
12462
  strategyName,
11940
12463
  });
11941
12464
  const storage = this.getStorage(symbol, strategyName);
11942
- return storage.getReport(symbol, strategyName);
12465
+ return storage.getReport(symbol, strategyName, columns);
11943
12466
  };
11944
12467
  /**
11945
12468
  * Saves symbol-strategy report to disk.
@@ -11949,6 +12472,7 @@ class PartialMarkdownService {
11949
12472
  * @param symbol - Trading pair symbol to save report for
11950
12473
  * @param strategyName - Strategy name to save report for
11951
12474
  * @param path - Directory path to save report (default: "./dump/partial")
12475
+ * @param columns - Column configuration for formatting the table
11952
12476
  *
11953
12477
  * @example
11954
12478
  * ```typescript
@@ -11961,14 +12485,14 @@ class PartialMarkdownService {
11961
12485
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
11962
12486
  * ```
11963
12487
  */
11964
- this.dump = async (symbol, strategyName, path = "./dump/partial") => {
12488
+ this.dump = async (symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) => {
11965
12489
  this.loggerService.log("partialMarkdownService dump", {
11966
12490
  symbol,
11967
12491
  strategyName,
11968
12492
  path,
11969
12493
  });
11970
12494
  const storage = this.getStorage(symbol, strategyName);
11971
- await storage.dump(symbol, strategyName, path);
12495
+ await storage.dump(symbol, strategyName, path, columns);
11972
12496
  };
11973
12497
  /**
11974
12498
  * Clears accumulated event data from storage.
@@ -12428,92 +12952,6 @@ class ConfigValidationService {
12428
12952
  }
12429
12953
  }
12430
12954
 
12431
- const columns = [
12432
- {
12433
- key: "symbol",
12434
- label: "Symbol",
12435
- format: (data) => data.symbol,
12436
- isVisible: () => true,
12437
- },
12438
- {
12439
- key: "strategyName",
12440
- label: "Strategy",
12441
- format: (data) => data.strategyName,
12442
- isVisible: () => true,
12443
- },
12444
- {
12445
- key: "signalId",
12446
- label: "Signal ID",
12447
- format: (data) => data.pendingSignal.id || "N/A",
12448
- isVisible: () => true,
12449
- },
12450
- {
12451
- key: "position",
12452
- label: "Position",
12453
- format: (data) => data.pendingSignal.position.toUpperCase(),
12454
- isVisible: () => true,
12455
- },
12456
- {
12457
- key: "note",
12458
- label: "Note",
12459
- format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
12460
- isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
12461
- },
12462
- {
12463
- key: "exchangeName",
12464
- label: "Exchange",
12465
- format: (data) => data.exchangeName,
12466
- isVisible: () => true,
12467
- },
12468
- {
12469
- key: "openPrice",
12470
- label: "Open Price",
12471
- format: (data) => data.pendingSignal.priceOpen !== undefined
12472
- ? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
12473
- : "N/A",
12474
- isVisible: () => true,
12475
- },
12476
- {
12477
- key: "takeProfit",
12478
- label: "Take Profit",
12479
- format: (data) => data.pendingSignal.priceTakeProfit !== undefined
12480
- ? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
12481
- : "N/A",
12482
- isVisible: () => true,
12483
- },
12484
- {
12485
- key: "stopLoss",
12486
- label: "Stop Loss",
12487
- format: (data) => data.pendingSignal.priceStopLoss !== undefined
12488
- ? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
12489
- : "N/A",
12490
- isVisible: () => true,
12491
- },
12492
- {
12493
- key: "currentPrice",
12494
- label: "Current Price",
12495
- format: (data) => `${data.currentPrice.toFixed(8)} USD`,
12496
- isVisible: () => true,
12497
- },
12498
- {
12499
- key: "activePositionCount",
12500
- label: "Active Positions",
12501
- format: (data) => data.activePositionCount.toString(),
12502
- isVisible: () => true,
12503
- },
12504
- {
12505
- key: "comment",
12506
- label: "Reason",
12507
- format: (data) => data.comment,
12508
- isVisible: () => true,
12509
- },
12510
- {
12511
- key: "timestamp",
12512
- label: "Timestamp",
12513
- format: (data) => new Date(data.timestamp).toISOString(),
12514
- isVisible: () => true,
12515
- },
12516
- ];
12517
12955
  /** Maximum number of events to store in risk reports */
12518
12956
  const MAX_EVENTS = 250;
12519
12957
  /**
@@ -12569,9 +13007,10 @@ class ReportStorage {
12569
13007
  *
12570
13008
  * @param symbol - Trading pair symbol
12571
13009
  * @param strategyName - Strategy name
13010
+ * @param columns - Column configuration for formatting the table
12572
13011
  * @returns Markdown formatted report with all events
12573
13012
  */
12574
- async getReport(symbol, strategyName) {
13013
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) {
12575
13014
  const stats = await this.getData();
12576
13015
  if (stats.totalRejections === 0) {
12577
13016
  return [
@@ -12580,10 +13019,15 @@ class ReportStorage {
12580
13019
  "No risk rejections recorded yet.",
12581
13020
  ].join("\n");
12582
13021
  }
12583
- const visibleColumns = columns.filter((col) => col.isVisible());
13022
+ const visibleColumns = [];
13023
+ for (const col of columns) {
13024
+ if (await col.isVisible()) {
13025
+ visibleColumns.push(col);
13026
+ }
13027
+ }
12584
13028
  const header = visibleColumns.map((col) => col.label);
12585
13029
  const separator = visibleColumns.map(() => "---");
12586
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
13030
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
12587
13031
  const tableData = [header, separator, ...rows];
12588
13032
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
12589
13033
  return [
@@ -12606,9 +13050,10 @@ class ReportStorage {
12606
13050
  * @param symbol - Trading pair symbol
12607
13051
  * @param strategyName - Strategy name
12608
13052
  * @param path - Directory path to save report (default: "./dump/risk")
13053
+ * @param columns - Column configuration for formatting the table
12609
13054
  */
12610
- async dump(symbol, strategyName, path$1 = "./dump/risk") {
12611
- const markdown = await this.getReport(symbol, strategyName);
13055
+ async dump(symbol, strategyName, path$1 = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
13056
+ const markdown = await this.getReport(symbol, strategyName, columns);
12612
13057
  try {
12613
13058
  const dir = path.join(process.cwd(), path$1);
12614
13059
  await fs.mkdir(dir, { recursive: true });
@@ -12700,6 +13145,7 @@ class RiskMarkdownService {
12700
13145
  *
12701
13146
  * @param symbol - Trading pair symbol to generate report for
12702
13147
  * @param strategyName - Strategy name to generate report for
13148
+ * @param columns - Column configuration for formatting the table
12703
13149
  * @returns Markdown formatted report string with table of all events
12704
13150
  *
12705
13151
  * @example
@@ -12709,13 +13155,13 @@ class RiskMarkdownService {
12709
13155
  * console.log(markdown);
12710
13156
  * ```
12711
13157
  */
12712
- this.getReport = async (symbol, strategyName) => {
13158
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) => {
12713
13159
  this.loggerService.log("riskMarkdownService getReport", {
12714
13160
  symbol,
12715
13161
  strategyName,
12716
13162
  });
12717
13163
  const storage = this.getStorage(symbol, strategyName);
12718
- return storage.getReport(symbol, strategyName);
13164
+ return storage.getReport(symbol, strategyName, columns);
12719
13165
  };
12720
13166
  /**
12721
13167
  * Saves symbol-strategy report to disk.
@@ -12725,6 +13171,7 @@ class RiskMarkdownService {
12725
13171
  * @param symbol - Trading pair symbol to save report for
12726
13172
  * @param strategyName - Strategy name to save report for
12727
13173
  * @param path - Directory path to save report (default: "./dump/risk")
13174
+ * @param columns - Column configuration for formatting the table
12728
13175
  *
12729
13176
  * @example
12730
13177
  * ```typescript
@@ -12737,14 +13184,14 @@ class RiskMarkdownService {
12737
13184
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
12738
13185
  * ```
12739
13186
  */
12740
- this.dump = async (symbol, strategyName, path = "./dump/risk") => {
13187
+ this.dump = async (symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) => {
12741
13188
  this.loggerService.log("riskMarkdownService dump", {
12742
13189
  symbol,
12743
13190
  strategyName,
12744
13191
  path,
12745
13192
  });
12746
13193
  const storage = this.getStorage(symbol, strategyName);
12747
- await storage.dump(symbol, strategyName, path);
13194
+ await storage.dump(symbol, strategyName, path, columns);
12748
13195
  };
12749
13196
  /**
12750
13197
  * Clears accumulated event data from storage.
@@ -12794,6 +13241,118 @@ class RiskMarkdownService {
12794
13241
  }
12795
13242
  }
12796
13243
 
13244
+ /**
13245
+ * Service for validating column configurations to ensure consistency with ColumnModel interface
13246
+ * and prevent invalid column definitions.
13247
+ *
13248
+ * Performs comprehensive validation on all column definitions in COLUMN_CONFIG:
13249
+ * - **Required fields**: All columns must have key, label, format, and isVisible properties
13250
+ * - **Unique keys**: All key values must be unique within each column collection
13251
+ * - **Function validation**: format and isVisible must be callable functions
13252
+ * - **Data types**: key and label must be non-empty strings
13253
+ *
13254
+ * @throws {Error} If any validation fails, throws with detailed breakdown of all errors
13255
+ *
13256
+ * @example
13257
+ * ```typescript
13258
+ * const validator = new ColumnValidationService();
13259
+ * validator.validate(); // Throws if column configuration is invalid
13260
+ * ```
13261
+ *
13262
+ * @example Validation failure output:
13263
+ * ```
13264
+ * Column configuration validation failed:
13265
+ * 1. backtest_columns[0]: Missing required field "format"
13266
+ * 2. heat_columns: Duplicate key "symbol" at indexes 1, 5
13267
+ * 3. live_columns[3].isVisible must be a function, got "boolean"
13268
+ * ```
13269
+ */
13270
+ class ColumnValidationService {
13271
+ constructor() {
13272
+ /**
13273
+ * @private
13274
+ * @readonly
13275
+ * Injected logger service instance
13276
+ */
13277
+ this.loggerService = inject(TYPES.loggerService);
13278
+ /**
13279
+ * Validates all column configurations in COLUMN_CONFIG for structural correctness.
13280
+ *
13281
+ * Checks:
13282
+ * 1. All required fields (key, label, format, isVisible) are present in each column
13283
+ * 2. key and label are non-empty strings
13284
+ * 3. format and isVisible are functions (not other types)
13285
+ * 4. All keys are unique within each column collection
13286
+ *
13287
+ * @throws Error if configuration is invalid
13288
+ */
13289
+ this.validate = () => {
13290
+ this.loggerService.log("columnValidationService validate");
13291
+ const errors = [];
13292
+ // Iterate through all column collections in COLUMN_CONFIG
13293
+ for (const [configKey, columns] of Object.entries(COLUMN_CONFIG)) {
13294
+ if (!Array.isArray(columns)) {
13295
+ errors.push(`${configKey} is not an array, got ${typeof columns}`);
13296
+ continue;
13297
+ }
13298
+ // Track keys for uniqueness check
13299
+ const keyMap = new Map();
13300
+ // Validate each column in the collection
13301
+ columns.forEach((column, index) => {
13302
+ if (!column || typeof column !== "object") {
13303
+ errors.push(`${configKey}[${index}]: Column must be an object, got ${typeof column}`);
13304
+ return;
13305
+ }
13306
+ // Check for all required fields
13307
+ const requiredFields = ["key", "label", "format", "isVisible"];
13308
+ for (const field of requiredFields) {
13309
+ if (!(field in column)) {
13310
+ errors.push(`${configKey}[${index}]: Missing required field "${field}"`);
13311
+ }
13312
+ }
13313
+ // Validate key and label are non-empty strings
13314
+ if (typeof column.key !== "string" || column.key.trim() === "") {
13315
+ errors.push(`${configKey}[${index}].key must be a non-empty string, got ${typeof column.key === "string" ? `"${column.key}"` : typeof column.key}`);
13316
+ }
13317
+ else {
13318
+ // Track key for uniqueness check
13319
+ if (!keyMap.has(column.key)) {
13320
+ keyMap.set(column.key, []);
13321
+ }
13322
+ keyMap.get(column.key).push(index);
13323
+ }
13324
+ if (typeof column.label !== "string" || column.label.trim() === "") {
13325
+ errors.push(`${configKey}[${index}].label must be a non-empty string, got ${typeof column.label === "string" ? `"${column.label}"` : typeof column.label}`);
13326
+ }
13327
+ // Validate format is a function
13328
+ if (typeof column.format !== "function") {
13329
+ errors.push(`${configKey}[${index}].format must be a function, got "${typeof column.format}"`);
13330
+ }
13331
+ // Validate isVisible is a function
13332
+ if (typeof column.isVisible !== "function") {
13333
+ errors.push(`${configKey}[${index}].isVisible must be a function, got "${typeof column.isVisible}"`);
13334
+ }
13335
+ });
13336
+ // Check for duplicate keys
13337
+ for (const [key, indexes] of keyMap.entries()) {
13338
+ if (indexes.length > 1) {
13339
+ errors.push(`${configKey}: Duplicate key "${key}" at indexes ${indexes.join(", ")}`);
13340
+ }
13341
+ }
13342
+ }
13343
+ // Throw aggregated errors if any
13344
+ if (errors.length > 0) {
13345
+ const errorMessage = `Column configuration validation failed:\n${errors
13346
+ .map((e, i) => ` ${i + 1}. ${e}`)
13347
+ .join("\n")}`;
13348
+ this.loggerService.warn(errorMessage);
13349
+ throw new Error(errorMessage);
13350
+ }
13351
+ this.loggerService.log("columnValidationService validation passed");
13352
+ };
13353
+ }
13354
+ }
13355
+
12797
13356
  {
12798
13357
  provide(TYPES.loggerService, () => new LoggerService());
12799
13358
  }
@@ -12865,6 +13424,7 @@ class RiskMarkdownService {
12865
13424
  provide(TYPES.riskValidationService, () => new RiskValidationService());
12866
13425
  provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
12867
13426
  provide(TYPES.configValidationService, () => new ConfigValidationService());
13427
+ provide(TYPES.columnValidationService, () => new ColumnValidationService());
12868
13428
  }
12869
13429
  {
12870
13430
  provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
@@ -12941,6 +13501,7 @@ const validationServices = {
12941
13501
  riskValidationService: inject(TYPES.riskValidationService),
12942
13502
  optimizerValidationService: inject(TYPES.optimizerValidationService),
12943
13503
  configValidationService: inject(TYPES.configValidationService),
13504
+ columnValidationService: inject(TYPES.columnValidationService),
12944
13505
  };
12945
13506
  const templateServices = {
12946
13507
  optimizerTemplateService: inject(TYPES.optimizerTemplateService),
@@ -13040,6 +13601,77 @@ function getConfig() {
13040
13601
  function getDefaultConfig() {
13041
13602
  return DEFAULT_CONFIG;
13042
13603
  }
13604
+ /**
13605
+ * Sets custom column configurations for markdown report generation.
13606
+ *
13607
+ * Allows overriding default column definitions for any report type.
13608
+ * All columns are validated before assignment to ensure structural correctness.
13609
+ *
13610
+ * @param columns - Partial column configuration object to override default column settings
13611
+ * @param _unsafe - Skip column validations - required for testbed
13612
+ *
13613
+ * @example
13614
+ * ```typescript
13615
+ * setColumns({
13616
+ * backtest_columns: [
13617
+ * {
13618
+ * key: "customId",
13619
+ * label: "Custom ID",
13620
+ * format: (data) => data.signal.id,
13621
+ * isVisible: () => true
13622
+ * }
13623
+ * ],
13624
+ * });
13625
+ * ```
13626
+ *
13627
+ * @throws {Error} If column configuration is invalid
13628
+ */
13629
+ function setColumns(columns, _unsafe) {
13630
+ const prevConfig = Object.assign({}, COLUMN_CONFIG);
13631
+ try {
13632
+ Object.assign(COLUMN_CONFIG, columns);
13633
+ !_unsafe && backtest$1.columnValidationService.validate();
13634
+ }
13635
+ catch (error) {
13636
+ console.warn(`backtest-kit setColumns failed: ${functoolsKit.getErrorMessage(error)}`, columns);
13637
+ Object.assign(COLUMN_CONFIG, prevConfig);
13638
+ throw error;
13639
+ }
13640
+ }
13641
+ /**
13642
+ * Retrieves a copy of the current column configuration for markdown report generation.
13643
+ *
13644
+ * Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
13645
+ * Use this to inspect the current column definitions without modifying them.
13646
+ *
13647
+ * @returns {ColumnConfig} A copy of the current column configuration object
13648
+ *
13649
+ * @example
13650
+ * ```typescript
13651
+ * const currentColumns = getColumns();
13652
+ * console.log(currentColumns.backtest_columns.length);
13653
+ * ```
13654
+ */
13655
+ function getColumns() {
13656
+ return Object.assign({}, COLUMN_CONFIG);
13657
+ }
13658
+ /**
13659
+ * Retrieves the default column configuration object for markdown report generation.
13660
+ *
13661
+ * Returns a reference to the default column definitions with all preset values.
13662
+ * Use this to see what column options are available and their default definitions.
13663
+ *
13664
+ * @returns {ColumnConfig} The default column configuration object
13665
+ *
13666
+ * @example
13667
+ * ```typescript
13668
+ * const defaultColumns = getDefaultColumns();
13669
+ * console.log(defaultColumns.backtest_columns);
13670
+ * ```
13671
+ */
13672
+ function getDefaultColumns() {
13673
+ return DEFAULT_COLUMNS;
13674
+ }
13043
13675
 
13044
13676
  const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
13045
13677
  const ADD_EXCHANGE_METHOD_NAME = "add.addExchange";
@@ -15025,6 +15657,7 @@ class BacktestInstance {
15025
15657
  *
15026
15658
  * @param symbol - Trading pair symbol
15027
15659
  * @param strategyName - Strategy name to generate report for
15660
+ * @param columns - Optional columns configuration for the report
15028
15661
  * @returns Promise resolving to markdown formatted report string
15029
15662
  *
15030
15663
  * @example
@@ -15034,12 +15667,12 @@ class BacktestInstance {
15034
15667
  * console.log(markdown);
15035
15668
  * ```
15036
15669
  */
15037
- this.getReport = async (symbol, strategyName) => {
15670
+ this.getReport = async (symbol, strategyName, columns) => {
15038
15671
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_REPORT, {
15039
15672
  symbol,
15040
15673
  strategyName,
15041
15674
  });
15042
- return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName);
15675
+ return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName, columns);
15043
15676
  };
15044
15677
  /**
15045
15678
  * Saves strategy report to disk.
@@ -15047,6 +15680,7 @@ class BacktestInstance {
15047
15680
  * @param symbol - Trading pair symbol
15048
15681
  * @param strategyName - Strategy name to save report for
15049
15682
  * @param path - Optional directory path to save report (default: "./dump/backtest")
15683
+ * @param columns - Optional columns configuration for the report
15050
15684
  *
15051
15685
  * @example
15052
15686
  * ```typescript
@@ -15058,13 +15692,13 @@ class BacktestInstance {
15058
15692
  * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
15059
15693
  * ```
15060
15694
  */
15061
- this.dump = async (symbol, strategyName, path) => {
15695
+ this.dump = async (symbol, strategyName, path, columns) => {
15062
15696
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_DUMP, {
15063
15697
  symbol,
15064
15698
  strategyName,
15065
15699
  path,
15066
15700
  });
15067
- await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path);
15701
+ await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path, columns);
15068
15702
  };
15069
15703
  }
15070
15704
  }
@@ -15203,6 +15837,7 @@ class BacktestUtils {
15203
15837
  *
15204
15838
  * @param symbol - Trading pair symbol
15205
15839
  * @param strategyName - Strategy name to generate report for
15840
+ * @param columns - Optional columns configuration for the report
15206
15841
  * @returns Promise resolving to markdown formatted report string
15207
15842
  *
15208
15843
  * @example
@@ -15211,7 +15846,7 @@ class BacktestUtils {
15211
15846
  * console.log(markdown);
15212
15847
  * ```
15213
15848
  */
15214
- this.getReport = async (symbol, strategyName) => {
15849
+ this.getReport = async (symbol, strategyName, columns) => {
15215
15850
  backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
15216
15851
  {
15217
15852
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15219,7 +15854,7 @@ class BacktestUtils {
15219
15854
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_REPORT));
15220
15855
  }
15221
15856
  const instance = this._getInstance(symbol, strategyName);
15222
- return await instance.getReport(symbol, strategyName);
15857
+ return await instance.getReport(symbol, strategyName, columns);
15223
15858
  };
15224
15859
  /**
15225
15860
  * Saves strategy report to disk.
@@ -15227,6 +15862,7 @@ class BacktestUtils {
15227
15862
  * @param symbol - Trading pair symbol
15228
15863
  * @param strategyName - Strategy name to save report for
15229
15864
  * @param path - Optional directory path to save report (default: "./dump/backtest")
15865
+ * @param columns - Optional columns configuration for the report
15230
15866
  *
15231
15867
  * @example
15232
15868
  * ```typescript
@@ -15237,7 +15873,7 @@ class BacktestUtils {
15237
15873
  * await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
15238
15874
  * ```
15239
15875
  */
15240
- this.dump = async (symbol, strategyName, path) => {
15876
+ this.dump = async (symbol, strategyName, path, columns) => {
15241
15877
  backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_DUMP);
15242
15878
  {
15243
15879
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15245,7 +15881,7 @@ class BacktestUtils {
15245
15881
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_DUMP));
15246
15882
  }
15247
15883
  const instance = this._getInstance(symbol, strategyName);
15248
- return await instance.dump(symbol, strategyName, path);
15884
+ return await instance.dump(symbol, strategyName, path, columns);
15249
15885
  };
15250
15886
  /**
15251
15887
  * Lists all active backtest instances with their current status.
@@ -15525,6 +16161,7 @@ class LiveInstance {
15525
16161
  *
15526
16162
  * @param symbol - Trading pair symbol
15527
16163
  * @param strategyName - Strategy name to generate report for
16164
+ * @param columns - Optional columns configuration for the report
15528
16165
  * @returns Promise resolving to markdown formatted report string
15529
16166
  *
15530
16167
  * @example
@@ -15534,12 +16171,12 @@ class LiveInstance {
15534
16171
  * console.log(markdown);
15535
16172
  * ```
15536
16173
  */
15537
- this.getReport = async (symbol, strategyName) => {
16174
+ this.getReport = async (symbol, strategyName, columns) => {
15538
16175
  backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
15539
16176
  symbol,
15540
16177
  strategyName,
15541
16178
  });
15542
- return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
16179
+ return await backtest$1.liveMarkdownService.getReport(symbol, strategyName, columns);
15543
16180
  };
15544
16181
  /**
15545
16182
  * Saves strategy report to disk.
@@ -15547,6 +16184,7 @@ class LiveInstance {
15547
16184
  * @param symbol - Trading pair symbol
15548
16185
  * @param strategyName - Strategy name to save report for
15549
16186
  * @param path - Optional directory path to save report (default: "./dump/live")
16187
+ * @param columns - Optional columns configuration for the report
15550
16188
  *
15551
16189
  * @example
15552
16190
  * ```typescript
@@ -15558,13 +16196,13 @@ class LiveInstance {
15558
16196
  * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
15559
16197
  * ```
15560
16198
  */
15561
- this.dump = async (symbol, strategyName, path) => {
16199
+ this.dump = async (symbol, strategyName, path, columns) => {
15562
16200
  backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
15563
16201
  symbol,
15564
16202
  strategyName,
15565
16203
  path,
15566
16204
  });
15567
- await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
16205
+ await backtest$1.liveMarkdownService.dump(symbol, strategyName, path, columns);
15568
16206
  };
15569
16207
  }
15570
16208
  }
@@ -15714,6 +16352,7 @@ class LiveUtils {
15714
16352
  *
15715
16353
  * @param symbol - Trading pair symbol
15716
16354
  * @param strategyName - Strategy name to generate report for
16355
+ * @param columns - Optional columns configuration for the report
15717
16356
  * @returns Promise resolving to markdown formatted report string
15718
16357
  *
15719
16358
  * @example
@@ -15722,7 +16361,7 @@ class LiveUtils {
15722
16361
  * console.log(markdown);
15723
16362
  * ```
15724
16363
  */
15725
- this.getReport = async (symbol, strategyName) => {
16364
+ this.getReport = async (symbol, strategyName, columns) => {
15726
16365
  backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_GET_REPORT);
15727
16366
  {
15728
16367
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15730,7 +16369,7 @@ class LiveUtils {
15730
16369
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
15731
16370
  }
15732
16371
  const instance = this._getInstance(symbol, strategyName);
15733
- return await instance.getReport(symbol, strategyName);
16372
+ return await instance.getReport(symbol, strategyName, columns);
15734
16373
  };
15735
16374
  /**
15736
16375
  * Saves strategy report to disk.
@@ -15738,6 +16377,7 @@ class LiveUtils {
15738
16377
  * @param symbol - Trading pair symbol
15739
16378
  * @param strategyName - Strategy name to save report for
15740
16379
  * @param path - Optional directory path to save report (default: "./dump/live")
16380
+ * @param columns - Optional columns configuration for the report
15741
16381
  *
15742
16382
  * @example
15743
16383
  * ```typescript
@@ -15748,7 +16388,7 @@ class LiveUtils {
15748
16388
  * await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
15749
16389
  * ```
15750
16390
  */
15751
- this.dump = async (symbol, strategyName, path) => {
16391
+ this.dump = async (symbol, strategyName, path, columns) => {
15752
16392
  backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_DUMP);
15753
16393
  {
15754
16394
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15756,7 +16396,7 @@ class LiveUtils {
15756
16396
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
15757
16397
  }
15758
16398
  const instance = this._getInstance(symbol, strategyName);
15759
- return await instance.dump(symbol, strategyName, path);
16399
+ return await instance.dump(symbol, strategyName, path, columns);
15760
16400
  };
15761
16401
  /**
15762
16402
  * Lists all active live trading instances with their current status.
@@ -15855,6 +16495,7 @@ class ScheduleUtils {
15855
16495
  *
15856
16496
  * @param symbol - Trading pair symbol
15857
16497
  * @param strategyName - Strategy name to generate report for
16498
+ * @param columns - Optional columns configuration for the report
15858
16499
  * @returns Promise resolving to markdown formatted report string
15859
16500
  *
15860
16501
  * @example
@@ -15863,7 +16504,7 @@ class ScheduleUtils {
15863
16504
  * console.log(markdown);
15864
16505
  * ```
15865
16506
  */
15866
- this.getReport = async (symbol, strategyName) => {
16507
+ this.getReport = async (symbol, strategyName, columns) => {
15867
16508
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_REPORT, {
15868
16509
  symbol,
15869
16510
  strategyName,
@@ -15874,7 +16515,7 @@ class ScheduleUtils {
15874
16515
  riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
15875
16516
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT));
15876
16517
  }
15877
- return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName);
16518
+ return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName, columns);
15878
16519
  };
15879
16520
  /**
15880
16521
  * Saves strategy report to disk.
@@ -15882,6 +16523,7 @@ class ScheduleUtils {
15882
16523
  * @param symbol - Trading pair symbol
15883
16524
  * @param strategyName - Strategy name to save report for
15884
16525
  * @param path - Optional directory path to save report (default: "./dump/schedule")
16526
+ * @param columns - Optional columns configuration for the report
15885
16527
  *
15886
16528
  * @example
15887
16529
  * ```typescript
@@ -15892,7 +16534,7 @@ class ScheduleUtils {
15892
16534
  * await Schedule.dump("BTCUSDT", "my-strategy", "./custom/path");
15893
16535
  * ```
15894
16536
  */
15895
- this.dump = async (symbol, strategyName, path) => {
16537
+ this.dump = async (symbol, strategyName, path, columns) => {
15896
16538
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_DUMP, {
15897
16539
  symbol,
15898
16540
  strategyName,
@@ -15904,7 +16546,7 @@ class ScheduleUtils {
15904
16546
  riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
15905
16547
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP));
15906
16548
  }
15907
- await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path);
16549
+ await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path, columns);
15908
16550
  };
15909
16551
  }
15910
16552
  }
@@ -16004,6 +16646,7 @@ class Performance {
16004
16646
  *
16005
16647
  * @param symbol - Trading pair symbol
16006
16648
  * @param strategyName - Strategy name to generate report for
16649
+ * @param columns - Optional columns configuration for the report
16007
16650
  * @returns Markdown formatted report string
16008
16651
  *
16009
16652
  * @example
@@ -16016,14 +16659,14 @@ class Performance {
16016
16659
  * await fs.writeFile("performance-report.md", markdown);
16017
16660
  * ```
16018
16661
  */
16019
- static async getReport(symbol, strategyName) {
16662
+ static async getReport(symbol, strategyName, columns) {
16020
16663
  backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
16021
16664
  {
16022
16665
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16023
16666
  riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
16024
16667
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT));
16025
16668
  }
16026
- return backtest$1.performanceMarkdownService.getReport(symbol, strategyName);
16669
+ return backtest$1.performanceMarkdownService.getReport(symbol, strategyName, columns);
16027
16670
  }
16028
16671
  /**
16029
16672
  * Saves performance report to disk.
@@ -16034,6 +16677,7 @@ class Performance {
16034
16677
  * @param symbol - Trading pair symbol
16035
16678
  * @param strategyName - Strategy name to save report for
16036
16679
  * @param path - Optional custom directory path
16680
+ * @param columns - Optional columns configuration for the report
16037
16681
  *
16038
16682
  * @example
16039
16683
  * ```typescript
@@ -16044,14 +16688,14 @@ class Performance {
16044
16688
  * await Performance.dump("BTCUSDT", "my-strategy", "./reports/perf");
16045
16689
  * ```
16046
16690
  */
16047
- static async dump(symbol, strategyName, path = "./dump/performance") {
16691
+ static async dump(symbol, strategyName, path = "./dump/performance", columns) {
16048
16692
  backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_DUMP);
16049
16693
  {
16050
16694
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16051
16695
  riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
16052
16696
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP));
16053
16697
  }
16054
- return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path);
16698
+ return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path, columns);
16055
16699
  }
16056
16700
  }
16057
16701
 
@@ -16313,6 +16957,8 @@ class WalkerInstance {
16313
16957
  *
16314
16958
  * @param symbol - Trading symbol
16315
16959
  * @param walkerName - Walker name to generate report for
16960
+ * @param strategyColumns - Optional strategy columns configuration
16961
+ * @param pnlColumns - Optional PNL columns configuration
16316
16962
  * @returns Promise resolving to markdown formatted report string
16317
16963
  *
16318
16964
  * @example
@@ -16322,7 +16968,7 @@ class WalkerInstance {
16322
16968
  * console.log(markdown);
16323
16969
  * ```
16324
16970
  */
16325
- this.getReport = async (symbol, walkerName) => {
16971
+ this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
16326
16972
  backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_REPORT, {
16327
16973
  symbol,
16328
16974
  walkerName,
@@ -16331,7 +16977,7 @@ class WalkerInstance {
16331
16977
  return await backtest$1.walkerMarkdownService.getReport(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
16332
16978
  exchangeName: walkerSchema.exchangeName,
16333
16979
  frameName: walkerSchema.frameName,
16334
- });
16980
+ }, strategyColumns, pnlColumns);
16335
16981
  };
16336
16982
  /**
16337
16983
  * Saves walker report to disk.
@@ -16339,6 +16985,8 @@ class WalkerInstance {
16339
16985
  * @param symbol - Trading symbol
16340
16986
  * @param walkerName - Walker name to save report for
16341
16987
  * @param path - Optional directory path to save report (default: "./dump/walker")
16988
+ * @param strategyColumns - Optional strategy columns configuration
16989
+ * @param pnlColumns - Optional PNL columns configuration
16342
16990
  *
16343
16991
  * @example
16344
16992
  * ```typescript
@@ -16350,7 +16998,7 @@ class WalkerInstance {
16350
16998
  * await instance.dump("BTCUSDT", "my-walker", "./custom/path");
16351
16999
  * ```
16352
17000
  */
16353
- this.dump = async (symbol, walkerName, path) => {
17001
+ this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
16354
17002
  backtest$1.loggerService.info(WALKER_METHOD_NAME_DUMP, {
16355
17003
  symbol,
16356
17004
  walkerName,
@@ -16360,7 +17008,7 @@ class WalkerInstance {
16360
17008
  await backtest$1.walkerMarkdownService.dump(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
16361
17009
  exchangeName: walkerSchema.exchangeName,
16362
17010
  frameName: walkerSchema.frameName,
16363
- }, path);
17011
+ }, path, strategyColumns, pnlColumns);
16364
17012
  };
16365
17013
  }
16366
17014
  }
@@ -16509,6 +17157,8 @@ class WalkerUtils {
16509
17157
  *
16510
17158
  * @param symbol - Trading symbol
16511
17159
  * @param walkerName - Walker name to generate report for
17160
+ * @param strategyColumns - Optional strategy columns configuration
17161
+ * @param pnlColumns - Optional PNL columns configuration
16512
17162
  * @returns Promise resolving to markdown formatted report string
16513
17163
  *
16514
17164
  * @example
@@ -16517,7 +17167,7 @@ class WalkerUtils {
16517
17167
  * console.log(markdown);
16518
17168
  * ```
16519
17169
  */
16520
- this.getReport = async (symbol, walkerName) => {
17170
+ this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
16521
17171
  backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_GET_REPORT);
16522
17172
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
16523
17173
  for (const strategyName of walkerSchema.strategies) {
@@ -16527,7 +17177,7 @@ class WalkerUtils {
16527
17177
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT));
16528
17178
  }
16529
17179
  const instance = this._getInstance(symbol, walkerName);
16530
- return await instance.getReport(symbol, walkerName);
17180
+ return await instance.getReport(symbol, walkerName, strategyColumns, pnlColumns);
16531
17181
  };
16532
17182
  /**
16533
17183
  * Saves walker report to disk.
@@ -16535,6 +17185,8 @@ class WalkerUtils {
16535
17185
  * @param symbol - Trading symbol
16536
17186
  * @param walkerName - Walker name to save report for
16537
17187
  * @param path - Optional directory path to save report (default: "./dump/walker")
17188
+ * @param strategyColumns - Optional strategy columns configuration
17189
+ * @param pnlColumns - Optional PNL columns configuration
16538
17190
  *
16539
17191
  * @example
16540
17192
  * ```typescript
@@ -16545,7 +17197,7 @@ class WalkerUtils {
16545
17197
  * await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
16546
17198
  * ```
16547
17199
  */
16548
- this.dump = async (symbol, walkerName, path) => {
17200
+ this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
16549
17201
  backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_DUMP);
16550
17202
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
16551
17203
  for (const strategyName of walkerSchema.strategies) {
@@ -16555,7 +17207,7 @@ class WalkerUtils {
16555
17207
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP));
16556
17208
  }
16557
17209
  const instance = this._getInstance(symbol, walkerName);
16558
- return await instance.dump(symbol, walkerName, path);
17210
+ return await instance.dump(symbol, walkerName, path, strategyColumns, pnlColumns);
16559
17211
  };
16560
17212
  /**
16561
17213
  * Lists all active walker instances with their current status.
@@ -16661,6 +17313,7 @@ class HeatUtils {
16661
17313
  * Symbols are sorted by Total PNL descending.
16662
17314
  *
16663
17315
  * @param strategyName - Strategy name to generate heatmap report for
17316
+ * @param columns - Optional columns configuration for the report
16664
17317
  * @returns Promise resolving to markdown formatted report string
16665
17318
  *
16666
17319
  * @example
@@ -16679,7 +17332,7 @@ class HeatUtils {
16679
17332
  * // ...
16680
17333
  * ```
16681
17334
  */
16682
- this.getReport = async (strategyName) => {
17335
+ this.getReport = async (strategyName, columns) => {
16683
17336
  backtest$1.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName });
16684
17337
  backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_GET_REPORT);
16685
17338
  {
@@ -16687,7 +17340,7 @@ class HeatUtils {
16687
17340
  riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
16688
17341
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT));
16689
17342
  }
16690
- return await backtest$1.heatMarkdownService.getReport(strategyName);
17343
+ return await backtest$1.heatMarkdownService.getReport(strategyName, columns);
16691
17344
  };
16692
17345
  /**
16693
17346
  * Saves heatmap report to disk for a strategy.
@@ -16697,6 +17350,7 @@ class HeatUtils {
16697
17350
  *
16698
17351
  * @param strategyName - Strategy name to save heatmap report for
16699
17352
  * @param path - Optional directory path to save report (default: "./dump/heatmap")
17353
+ * @param columns - Optional columns configuration for the report
16700
17354
  *
16701
17355
  * @example
16702
17356
  * ```typescript
@@ -16707,7 +17361,7 @@ class HeatUtils {
16707
17361
  * await Heat.dump("my-strategy", "./reports");
16708
17362
  * ```
16709
17363
  */
16710
- this.dump = async (strategyName, path) => {
17364
+ this.dump = async (strategyName, path, columns) => {
16711
17365
  backtest$1.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName, path });
16712
17366
  backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_DUMP);
16713
17367
  {
@@ -16715,7 +17369,7 @@ class HeatUtils {
16715
17369
  riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
16716
17370
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP));
16717
17371
  }
16718
- await backtest$1.heatMarkdownService.dump(strategyName, path);
17372
+ await backtest$1.heatMarkdownService.dump(strategyName, path, columns);
16719
17373
  };
16720
17374
  }
16721
17375
  }
@@ -17015,7 +17669,7 @@ class PartialUtils {
17015
17669
  *
17016
17670
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17017
17671
  * @param strategyName - Strategy name (e.g., "my-strategy")
17018
- * @returns Promise resolving to PartialStatistics object with counts and event list
17672
+ * @returns Promise resolving to PartialStatisticsModel object with counts and event list
17019
17673
  *
17020
17674
  * @example
17021
17675
  * ```typescript
@@ -17059,6 +17713,7 @@ class PartialUtils {
17059
17713
  *
17060
17714
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17061
17715
  * @param strategyName - Strategy name (e.g., "my-strategy")
17716
+ * @param columns - Optional columns configuration for the report
17062
17717
  * @returns Promise resolving to markdown formatted report string
17063
17718
  *
17064
17719
  * @example
@@ -17079,7 +17734,7 @@ class PartialUtils {
17079
17734
  * // **Loss events:** 1
17080
17735
  * ```
17081
17736
  */
17082
- this.getReport = async (symbol, strategyName) => {
17737
+ this.getReport = async (symbol, strategyName, columns) => {
17083
17738
  backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName });
17084
17739
  backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
17085
17740
  {
@@ -17087,7 +17742,7 @@ class PartialUtils {
17087
17742
  riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
17088
17743
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT));
17089
17744
  }
17090
- return await backtest$1.partialMarkdownService.getReport(symbol, strategyName);
17745
+ return await backtest$1.partialMarkdownService.getReport(symbol, strategyName, columns);
17091
17746
  };
17092
17747
  /**
17093
17748
  * Generates and saves markdown report to file.
@@ -17104,6 +17759,7 @@ class PartialUtils {
17104
17759
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17105
17760
  * @param strategyName - Strategy name (e.g., "my-strategy")
17106
17761
  * @param path - Output directory path (default: "./dump/partial")
17762
+ * @param columns - Optional columns configuration for the report
17107
17763
  * @returns Promise that resolves when file is written
17108
17764
  *
17109
17765
  * @example
@@ -17120,7 +17776,7 @@ class PartialUtils {
17120
17776
  * }
17121
17777
  * ```
17122
17778
  */
17123
- this.dump = async (symbol, strategyName, path) => {
17779
+ this.dump = async (symbol, strategyName, path, columns) => {
17124
17780
  backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName, path });
17125
17781
  backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_DUMP);
17126
17782
  {
@@ -17128,7 +17784,7 @@ class PartialUtils {
17128
17784
  riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
17129
17785
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP));
17130
17786
  }
17131
- await backtest$1.partialMarkdownService.dump(symbol, strategyName, path);
17787
+ await backtest$1.partialMarkdownService.dump(symbol, strategyName, path, columns);
17132
17788
  };
17133
17789
  }
17134
17790
  }
@@ -17260,8 +17916,10 @@ exports.formatPrice = formatPrice;
17260
17916
  exports.formatQuantity = formatQuantity;
17261
17917
  exports.getAveragePrice = getAveragePrice;
17262
17918
  exports.getCandles = getCandles;
17919
+ exports.getColumns = getColumns;
17263
17920
  exports.getConfig = getConfig;
17264
17921
  exports.getDate = getDate;
17922
+ exports.getDefaultColumns = getDefaultColumns;
17265
17923
  exports.getDefaultConfig = getDefaultConfig;
17266
17924
  exports.getMode = getMode;
17267
17925
  exports.lib = backtest;
@@ -17300,5 +17958,6 @@ exports.listenWalker = listenWalker;
17300
17958
  exports.listenWalkerComplete = listenWalkerComplete;
17301
17959
  exports.listenWalkerOnce = listenWalkerOnce;
17302
17960
  exports.listenWalkerProgress = listenWalkerProgress;
17961
+ exports.setColumns = setColumns;
17303
17962
  exports.setConfig = setConfig;
17304
17963
  exports.setLogger = setLogger;