backtest-kit 1.5.25 → 1.5.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +3 -3
  2. package/build/index.cjs +1789 -1055
  3. package/build/index.mjs +1788 -1057
  4. package/package.json +1 -1
  5. package/types.d.ts +4549 -3961
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'),
@@ -1552,7 +2628,7 @@ const PersistScheduleAdapter = new PersistScheduleUtils();
1552
2628
  * Utility class for managing partial profit/loss levels persistence.
1553
2629
  *
1554
2630
  * Features:
1555
- * - Memoized storage instances per symbol
2631
+ * - Memoized storage instances per symbol:strategyName
1556
2632
  * - Custom adapter support
1557
2633
  * - Atomic read/write operations for partial data
1558
2634
  * - Crash-safe partial state management
@@ -1562,23 +2638,25 @@ const PersistScheduleAdapter = new PersistScheduleUtils();
1562
2638
  class PersistPartialUtils {
1563
2639
  constructor() {
1564
2640
  this.PersistPartialFactory = PersistBase;
1565
- this.getPartialStorage = functoolsKit.memoize(([symbol]) => `${symbol}`, (symbol) => Reflect.construct(this.PersistPartialFactory, [
1566
- symbol,
2641
+ this.getPartialStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => Reflect.construct(this.PersistPartialFactory, [
2642
+ `${symbol}_${strategyName}`,
1567
2643
  `./dump/data/partial/`,
1568
2644
  ]));
1569
2645
  /**
1570
- * Reads persisted partial data for a symbol.
2646
+ * Reads persisted partial data for a symbol and strategy.
1571
2647
  *
1572
2648
  * Called by ClientPartial.waitForInit() to restore state.
1573
2649
  * Returns empty object if no partial data exists.
1574
2650
  *
1575
2651
  * @param symbol - Trading pair symbol
2652
+ * @param strategyName - Strategy identifier
1576
2653
  * @returns Promise resolving to partial data record
1577
2654
  */
1578
- this.readPartialData = async (symbol) => {
2655
+ this.readPartialData = async (symbol, strategyName) => {
1579
2656
  backtest$1.loggerService.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
1580
- const isInitial = !this.getPartialStorage.has(symbol);
1581
- const stateStorage = this.getPartialStorage(symbol);
2657
+ const key = `${symbol}:${strategyName}`;
2658
+ const isInitial = !this.getPartialStorage.has(key);
2659
+ const stateStorage = this.getPartialStorage(symbol, strategyName);
1582
2660
  await stateStorage.waitForInit(isInitial);
1583
2661
  const PARTIAL_STORAGE_KEY = "levels";
1584
2662
  if (await stateStorage.hasValue(PARTIAL_STORAGE_KEY)) {
@@ -1594,12 +2672,14 @@ class PersistPartialUtils {
1594
2672
  *
1595
2673
  * @param partialData - Record of signal IDs to partial data
1596
2674
  * @param symbol - Trading pair symbol
2675
+ * @param strategyName - Strategy identifier
1597
2676
  * @returns Promise that resolves when write is complete
1598
2677
  */
1599
- this.writePartialData = async (partialData, symbol) => {
2678
+ this.writePartialData = async (partialData, symbol, strategyName) => {
1600
2679
  backtest$1.loggerService.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
1601
- const isInitial = !this.getPartialStorage.has(symbol);
1602
- const stateStorage = this.getPartialStorage(symbol);
2680
+ const key = `${symbol}:${strategyName}`;
2681
+ const isInitial = !this.getPartialStorage.has(key);
2682
+ const stateStorage = this.getPartialStorage(symbol, strategyName);
1603
2683
  await stateStorage.waitForInit(isInitial);
1604
2684
  const PARTIAL_STORAGE_KEY = "levels";
1605
2685
  await stateStorage.writeValue(PARTIAL_STORAGE_KEY, partialData);
@@ -1634,10 +2714,10 @@ class PersistPartialUtils {
1634
2714
  * PersistPartialAdapter.usePersistPartialAdapter(RedisPersist);
1635
2715
  *
1636
2716
  * // Read partial data
1637
- * const partialData = await PersistPartialAdapter.readPartialData("BTCUSDT");
2717
+ * const partialData = await PersistPartialAdapter.readPartialData("BTCUSDT", "my-strategy");
1638
2718
  *
1639
2719
  * // Write partial data
1640
- * await PersistPartialAdapter.writePartialData(partialData, "BTCUSDT");
2720
+ * await PersistPartialAdapter.writePartialData(partialData, "BTCUSDT", "my-strategy");
1641
2721
  * ```
1642
2722
  */
1643
2723
  const PersistPartialAdapter = new PersistPartialUtils();
@@ -1765,53 +2845,6 @@ var emitters = /*#__PURE__*/Object.freeze({
1765
2845
  walkerStopSubject: walkerStopSubject
1766
2846
  });
1767
2847
 
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
2848
  const INTERVAL_MINUTES$1 = {
1816
2849
  "1m": 1,
1817
2850
  "3m": 3,
@@ -3390,7 +4423,7 @@ class RiskUtils {
3390
4423
  *
3391
4424
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3392
4425
  * @param strategyName - Strategy name (e.g., "my-strategy")
3393
- * @returns Promise resolving to RiskStatistics object with counts and event list
4426
+ * @returns Promise resolving to RiskStatisticsModel object with counts and event list
3394
4427
  *
3395
4428
  * @example
3396
4429
  * ```typescript
@@ -3437,6 +4470,7 @@ class RiskUtils {
3437
4470
  *
3438
4471
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3439
4472
  * @param strategyName - Strategy name (e.g., "my-strategy")
4473
+ * @param columns - Optional columns configuration for the report
3440
4474
  * @returns Promise resolving to markdown formatted report string
3441
4475
  *
3442
4476
  * @example
@@ -3460,7 +4494,7 @@ class RiskUtils {
3460
4494
  * // - my-strategy: 1
3461
4495
  * ```
3462
4496
  */
3463
- this.getReport = async (symbol, strategyName) => {
4497
+ this.getReport = async (symbol, strategyName, columns) => {
3464
4498
  backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, {
3465
4499
  symbol,
3466
4500
  strategyName,
@@ -3472,7 +4506,7 @@ class RiskUtils {
3472
4506
  backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
3473
4507
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT));
3474
4508
  }
3475
- return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
4509
+ return await backtest$1.riskMarkdownService.getReport(symbol, strategyName, columns);
3476
4510
  };
3477
4511
  /**
3478
4512
  * Generates and saves markdown report to file.
@@ -3489,6 +4523,7 @@ class RiskUtils {
3489
4523
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
3490
4524
  * @param strategyName - Strategy name (e.g., "my-strategy")
3491
4525
  * @param path - Output directory path (default: "./dump/risk")
4526
+ * @param columns - Optional columns configuration for the report
3492
4527
  * @returns Promise that resolves when file is written
3493
4528
  *
3494
4529
  * @example
@@ -3505,7 +4540,7 @@ class RiskUtils {
3505
4540
  * }
3506
4541
  * ```
3507
4542
  */
3508
- this.dump = async (symbol, strategyName, path) => {
4543
+ this.dump = async (symbol, strategyName, path, columns) => {
3509
4544
  backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, {
3510
4545
  symbol,
3511
4546
  strategyName,
@@ -3518,7 +4553,7 @@ class RiskUtils {
3518
4553
  backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
3519
4554
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP));
3520
4555
  }
3521
- await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
4556
+ await backtest$1.riskMarkdownService.dump(symbol, strategyName, path, columns);
3522
4557
  };
3523
4558
  }
3524
4559
  }
@@ -3543,25 +4578,25 @@ const NOOP_RISK = {
3543
4578
  addSignal: () => Promise.resolve(),
3544
4579
  removeSignal: () => Promise.resolve(),
3545
4580
  };
3546
- const GET_RISK_FN = (dto, self) => {
4581
+ const GET_RISK_FN = (dto, backtest, self) => {
3547
4582
  const hasRiskName = !!dto.riskName;
3548
- const hasRiskList = !!(dto.riskList?.length);
4583
+ const hasRiskList = !!dto.riskList?.length;
3549
4584
  // Нет ни riskName, ни riskList
3550
4585
  if (!hasRiskName && !hasRiskList) {
3551
4586
  return NOOP_RISK;
3552
4587
  }
3553
4588
  // Есть только riskName (без riskList)
3554
4589
  if (hasRiskName && !hasRiskList) {
3555
- return self.riskConnectionService.getRisk(dto.riskName);
4590
+ return self.riskConnectionService.getRisk(dto.riskName, backtest);
3556
4591
  }
3557
4592
  // Есть только riskList (без riskName)
3558
4593
  if (!hasRiskName && hasRiskList) {
3559
- return new MergeRisk(dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName)));
4594
+ return new MergeRisk(dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName, backtest)));
3560
4595
  }
3561
4596
  // Есть и riskName, и riskList - объединяем (riskName в начало)
3562
4597
  return new MergeRisk([
3563
- self.riskConnectionService.getRisk(dto.riskName),
3564
- ...dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName))
4598
+ self.riskConnectionService.getRisk(dto.riskName, backtest),
4599
+ ...dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName, backtest)),
3565
4600
  ]);
3566
4601
  };
3567
4602
  /**
@@ -3603,7 +4638,7 @@ class StrategyConnectionService {
3603
4638
  * @param strategyName - Name of registered strategy schema
3604
4639
  * @returns Configured ClientStrategy instance
3605
4640
  */
3606
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => {
4641
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, backtest]) => `${symbol}:${strategyName}:${backtest ? "backtest" : "live"}`, (symbol, strategyName, backtest) => {
3607
4642
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
3608
4643
  return new ClientStrategy({
3609
4644
  symbol,
@@ -3616,7 +4651,7 @@ class StrategyConnectionService {
3616
4651
  risk: GET_RISK_FN({
3617
4652
  riskName,
3618
4653
  riskList,
3619
- }, this),
4654
+ }, backtest, this),
3620
4655
  riskName,
3621
4656
  strategyName,
3622
4657
  getSignal,
@@ -3633,12 +4668,13 @@ class StrategyConnectionService {
3633
4668
  *
3634
4669
  * @returns Promise resolving to pending signal or null
3635
4670
  */
3636
- this.getPendingSignal = async (symbol, strategyName) => {
4671
+ this.getPendingSignal = async (backtest, symbol, strategyName) => {
3637
4672
  this.loggerService.log("strategyConnectionService getPendingSignal", {
3638
4673
  symbol,
3639
4674
  strategyName,
4675
+ backtest,
3640
4676
  });
3641
- const strategy = this.getStrategy(symbol, strategyName);
4677
+ const strategy = this.getStrategy(symbol, strategyName, backtest);
3642
4678
  return await strategy.getPendingSignal(symbol, strategyName);
3643
4679
  };
3644
4680
  /**
@@ -3651,12 +4687,13 @@ class StrategyConnectionService {
3651
4687
  * @param strategyName - Name of the strategy
3652
4688
  * @returns Promise resolving to true if strategy is stopped, false otherwise
3653
4689
  */
3654
- this.getStopped = async (symbol, strategyName) => {
4690
+ this.getStopped = async (backtest, symbol, strategyName) => {
3655
4691
  this.loggerService.log("strategyConnectionService getStopped", {
3656
4692
  symbol,
3657
4693
  strategyName,
4694
+ backtest,
3658
4695
  });
3659
- const strategy = this.getStrategy(symbol, strategyName);
4696
+ const strategy = this.getStrategy(symbol, strategyName, backtest);
3660
4697
  return await strategy.getStopped(symbol, strategyName);
3661
4698
  };
3662
4699
  /**
@@ -3674,7 +4711,8 @@ class StrategyConnectionService {
3674
4711
  symbol,
3675
4712
  strategyName,
3676
4713
  });
3677
- const strategy = this.getStrategy(symbol, strategyName);
4714
+ const backtest = this.executionContextService.context.backtest;
4715
+ const strategy = this.getStrategy(symbol, strategyName, backtest);
3678
4716
  await strategy.waitForInit();
3679
4717
  const tick = await strategy.tick(symbol, strategyName);
3680
4718
  {
@@ -3705,7 +4743,8 @@ class StrategyConnectionService {
3705
4743
  strategyName,
3706
4744
  candleCount: candles.length,
3707
4745
  });
3708
- const strategy = this.getStrategy(symbol, strategyName);
4746
+ const backtest = this.executionContextService.context.backtest;
4747
+ const strategy = this.getStrategy(symbol, strategyName, backtest);
3709
4748
  await strategy.waitForInit();
3710
4749
  const tick = await strategy.backtest(symbol, strategyName, candles);
3711
4750
  {
@@ -3726,11 +4765,11 @@ class StrategyConnectionService {
3726
4765
  * @param strategyName - Name of strategy to stop
3727
4766
  * @returns Promise that resolves when stop flag is set
3728
4767
  */
3729
- this.stop = async (ctx, backtest) => {
4768
+ this.stop = async (backtest, ctx) => {
3730
4769
  this.loggerService.log("strategyConnectionService stop", {
3731
4770
  ctx,
3732
4771
  });
3733
- const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
4772
+ const strategy = this.getStrategy(ctx.symbol, ctx.strategyName, backtest);
3734
4773
  await strategy.stop(ctx.symbol, ctx.strategyName, backtest);
3735
4774
  };
3736
4775
  /**
@@ -3741,12 +4780,12 @@ class StrategyConnectionService {
3741
4780
  *
3742
4781
  * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
3743
4782
  */
3744
- this.clear = async (ctx) => {
4783
+ this.clear = async (backtest, ctx) => {
3745
4784
  this.loggerService.log("strategyConnectionService clear", {
3746
4785
  ctx,
3747
4786
  });
3748
4787
  if (ctx) {
3749
- const key = `${ctx.symbol}:${ctx.strategyName}`;
4788
+ const key = `${ctx.symbol}:${ctx.strategyName}:${backtest ? "backtest" : "live"}`;
3750
4789
  this.getStrategy.clear(key);
3751
4790
  }
3752
4791
  else {
@@ -4141,9 +5180,15 @@ const DO_VALIDATION_FN = functoolsKit.trycatch(async (validation, params) => {
4141
5180
  * Initializes active positions by reading from persistence.
4142
5181
  * Uses singleshot pattern to ensure it only runs once.
4143
5182
  * This function is exported for use in tests or other modules.
5183
+ *
5184
+ * In backtest mode, initializes with empty Map. In live mode, reads from persist storage.
4144
5185
  */
4145
5186
  const WAIT_FOR_INIT_FN$1 = async (self) => {
4146
- self.params.logger.debug("ClientRisk waitForInit");
5187
+ self.params.logger.debug("ClientRisk waitForInit", { backtest: self.params.backtest });
5188
+ if (self.params.backtest) {
5189
+ self._activePositions = new Map();
5190
+ return;
5191
+ }
4147
5192
  const persistedPositions = await PersistRiskAdapter.readPositionData(self.params.riskName);
4148
5193
  self._activePositions = new Map(persistedPositions);
4149
5194
  };
@@ -4191,6 +5236,7 @@ class ClientRisk {
4191
5236
  this.params.logger.debug("ClientRisk checkSignal", {
4192
5237
  symbol: params.symbol,
4193
5238
  strategyName: params.strategyName,
5239
+ backtest: this.params.backtest,
4194
5240
  });
4195
5241
  if (this._activePositions === POSITION_NEED_FETCH) {
4196
5242
  await this.waitForInit();
@@ -4236,8 +5282,12 @@ class ClientRisk {
4236
5282
  }
4237
5283
  /**
4238
5284
  * Persists current active positions to disk.
5285
+ * Skips in backtest mode.
4239
5286
  */
4240
5287
  async _updatePositions() {
5288
+ if (this.params.backtest) {
5289
+ return;
5290
+ }
4241
5291
  if (this._activePositions === POSITION_NEED_FETCH) {
4242
5292
  await this.waitForInit();
4243
5293
  }
@@ -4251,6 +5301,7 @@ class ClientRisk {
4251
5301
  this.params.logger.debug("ClientRisk addSignal", {
4252
5302
  symbol,
4253
5303
  context,
5304
+ backtest: this.params.backtest,
4254
5305
  });
4255
5306
  if (this._activePositions === POSITION_NEED_FETCH) {
4256
5307
  await this.waitForInit();
@@ -4273,6 +5324,7 @@ class ClientRisk {
4273
5324
  this.params.logger.debug("ClientRisk removeSignal", {
4274
5325
  symbol,
4275
5326
  context,
5327
+ backtest: this.params.backtest,
4276
5328
  });
4277
5329
  if (this._activePositions === POSITION_NEED_FETCH) {
4278
5330
  await this.waitForInit();
@@ -4343,19 +5395,21 @@ class RiskConnectionService {
4343
5395
  this.loggerService = inject(TYPES.loggerService);
4344
5396
  this.riskSchemaService = inject(TYPES.riskSchemaService);
4345
5397
  /**
4346
- * Retrieves memoized ClientRisk instance for given risk name.
5398
+ * Retrieves memoized ClientRisk instance for given risk name and backtest mode.
4347
5399
  *
4348
5400
  * Creates ClientRisk on first call, returns cached instance on subsequent calls.
4349
- * Cache key is riskName string.
5401
+ * Cache key is "riskName:backtest" string to separate live and backtest instances.
4350
5402
  *
4351
5403
  * @param riskName - Name of registered risk schema
5404
+ * @param backtest - True if backtest mode, false if live mode
4352
5405
  * @returns Configured ClientRisk instance
4353
5406
  */
4354
- this.getRisk = functoolsKit.memoize(([riskName]) => `${riskName}`, (riskName) => {
5407
+ this.getRisk = functoolsKit.memoize(([riskName, backtest]) => `${riskName}:${backtest ? "backtest" : "live"}`, (riskName, backtest) => {
4355
5408
  const schema = this.riskSchemaService.get(riskName);
4356
5409
  return new ClientRisk({
4357
5410
  ...schema,
4358
5411
  logger: this.loggerService,
5412
+ backtest,
4359
5413
  onRejected: COMMIT_REJECTION_FN,
4360
5414
  });
4361
5415
  });
@@ -4367,7 +5421,7 @@ class RiskConnectionService {
4367
5421
  * ClientRisk will emit riskSubject event via onRejected callback when signal is rejected.
4368
5422
  *
4369
5423
  * @param params - Risk check arguments (portfolio state, position details)
4370
- * @param context - Execution context with risk name
5424
+ * @param context - Execution context with risk name and backtest mode
4371
5425
  * @returns Promise resolving to risk check result
4372
5426
  */
4373
5427
  this.checkSignal = async (params, context) => {
@@ -4375,46 +5429,48 @@ class RiskConnectionService {
4375
5429
  symbol: params.symbol,
4376
5430
  context,
4377
5431
  });
4378
- return await this.getRisk(context.riskName).checkSignal(params);
5432
+ return await this.getRisk(context.riskName, context.backtest).checkSignal(params);
4379
5433
  };
4380
5434
  /**
4381
5435
  * Registers an opened signal with the risk management system.
4382
5436
  * Routes to appropriate ClientRisk instance.
4383
5437
  *
4384
5438
  * @param symbol - Trading pair symbol
4385
- * @param context - Context information (strategyName, riskName)
5439
+ * @param context - Context information (strategyName, riskName, backtest)
4386
5440
  */
4387
5441
  this.addSignal = async (symbol, context) => {
4388
5442
  this.loggerService.log("riskConnectionService addSignal", {
4389
5443
  symbol,
4390
5444
  context,
4391
5445
  });
4392
- await this.getRisk(context.riskName).addSignal(symbol, context);
5446
+ await this.getRisk(context.riskName, context.backtest).addSignal(symbol, context);
4393
5447
  };
4394
5448
  /**
4395
5449
  * Removes a closed signal from the risk management system.
4396
5450
  * Routes to appropriate ClientRisk instance.
4397
5451
  *
4398
5452
  * @param symbol - Trading pair symbol
4399
- * @param context - Context information (strategyName, riskName)
5453
+ * @param context - Context information (strategyName, riskName, backtest)
4400
5454
  */
4401
5455
  this.removeSignal = async (symbol, context) => {
4402
5456
  this.loggerService.log("riskConnectionService removeSignal", {
4403
5457
  symbol,
4404
5458
  context,
4405
5459
  });
4406
- await this.getRisk(context.riskName).removeSignal(symbol, context);
5460
+ await this.getRisk(context.riskName, context.backtest).removeSignal(symbol, context);
4407
5461
  };
4408
5462
  /**
4409
5463
  * Clears the cached ClientRisk instance for the given risk name.
4410
5464
  *
4411
5465
  * @param riskName - Name of the risk schema to clear from cache
4412
5466
  */
4413
- this.clear = async (riskName) => {
5467
+ this.clear = async (backtest, riskName) => {
4414
5468
  this.loggerService.log("riskConnectionService clear", {
4415
5469
  riskName,
5470
+ backtest,
4416
5471
  });
4417
- this.getRisk.clear(riskName);
5472
+ const key = `${riskName}:${backtest ? "backtest" : "live"}`;
5473
+ this.getRisk.clear(key);
4418
5474
  };
4419
5475
  }
4420
5476
  }
@@ -4637,7 +5693,7 @@ class StrategyCoreService {
4637
5693
  * @param strategyName - Name of the strategy
4638
5694
  * @returns Promise resolving to pending signal or null
4639
5695
  */
4640
- this.getPendingSignal = async (symbol, strategyName) => {
5696
+ this.getPendingSignal = async (backtest, symbol, strategyName) => {
4641
5697
  this.loggerService.log("strategyCoreService getPendingSignal", {
4642
5698
  symbol,
4643
5699
  strategyName,
@@ -4646,7 +5702,7 @@ class StrategyCoreService {
4646
5702
  throw new Error("strategyCoreService getPendingSignal requires a method context");
4647
5703
  }
4648
5704
  await this.validate(symbol, strategyName);
4649
- return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
5705
+ return await this.strategyConnectionService.getPendingSignal(backtest, symbol, strategyName);
4650
5706
  };
4651
5707
  /**
4652
5708
  * Checks if the strategy has been stopped.
@@ -4658,16 +5714,17 @@ class StrategyCoreService {
4658
5714
  * @param strategyName - Name of the strategy
4659
5715
  * @returns Promise resolving to true if strategy is stopped, false otherwise
4660
5716
  */
4661
- this.getStopped = async (symbol, strategyName) => {
5717
+ this.getStopped = async (backtest, symbol, strategyName) => {
4662
5718
  this.loggerService.log("strategyCoreService getStopped", {
4663
5719
  symbol,
4664
5720
  strategyName,
5721
+ backtest,
4665
5722
  });
4666
5723
  if (!MethodContextService.hasContext()) {
4667
5724
  throw new Error("strategyCoreService getStopped requires a method context");
4668
5725
  }
4669
5726
  await this.validate(symbol, strategyName);
4670
- return await this.strategyConnectionService.getStopped(symbol, strategyName);
5727
+ return await this.strategyConnectionService.getStopped(backtest, symbol, strategyName);
4671
5728
  };
4672
5729
  /**
4673
5730
  * Checks signal status at a specific timestamp.
@@ -4741,13 +5798,13 @@ class StrategyCoreService {
4741
5798
  * @param strategyName - Name of strategy to stop
4742
5799
  * @returns Promise that resolves when stop flag is set
4743
5800
  */
4744
- this.stop = async (ctx, backtest) => {
5801
+ this.stop = async (backtest, ctx) => {
4745
5802
  this.loggerService.log("strategyCoreService stop", {
4746
5803
  ctx,
4747
5804
  backtest,
4748
5805
  });
4749
5806
  await this.validate(ctx.symbol, ctx.strategyName);
4750
- return await this.strategyConnectionService.stop(ctx, backtest);
5807
+ return await this.strategyConnectionService.stop(backtest, ctx);
4751
5808
  };
4752
5809
  /**
4753
5810
  * Clears the memoized ClientStrategy instance from cache.
@@ -4757,14 +5814,14 @@ class StrategyCoreService {
4757
5814
  *
4758
5815
  * @param ctx - Optional context with symbol and strategyName (clears all if not provided)
4759
5816
  */
4760
- this.clear = async (ctx) => {
5817
+ this.clear = async (backtest, ctx) => {
4761
5818
  this.loggerService.log("strategyCoreService clear", {
4762
5819
  ctx,
4763
5820
  });
4764
5821
  if (ctx) {
4765
5822
  await this.validate(ctx.symbol, ctx.strategyName);
4766
5823
  }
4767
- return await this.strategyConnectionService.clear(ctx);
5824
+ return await this.strategyConnectionService.clear(backtest, ctx);
4768
5825
  };
4769
5826
  }
4770
5827
  }
@@ -4905,14 +5962,15 @@ class RiskGlobalService {
4905
5962
  * If no riskName is provided, clears all risk data.
4906
5963
  * @param riskName - Optional name of the risk instance to clear
4907
5964
  */
4908
- this.clear = async (riskName) => {
5965
+ this.clear = async (backtest, riskName) => {
4909
5966
  this.loggerService.log("riskGlobalService clear", {
4910
5967
  riskName,
5968
+ backtest,
4911
5969
  });
4912
5970
  if (riskName) {
4913
5971
  await this.validate(riskName);
4914
5972
  }
4915
- return await this.riskConnectionService.clear(riskName);
5973
+ return await this.riskConnectionService.clear(backtest, riskName);
4916
5974
  };
4917
5975
  }
4918
5976
  }
@@ -5035,6 +6093,19 @@ class StrategySchemaService {
5035
6093
  if (typeof strategySchema.strategyName !== "string") {
5036
6094
  throw new Error(`strategy schema validation failed: missing strategyName`);
5037
6095
  }
6096
+ if (strategySchema.riskName && typeof strategySchema.riskName !== "string") {
6097
+ throw new Error(`strategy schema validation failed: invalid riskName`);
6098
+ }
6099
+ if (strategySchema.riskList && !Array.isArray(strategySchema.riskList)) {
6100
+ throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} system=${strategySchema.riskList}`);
6101
+ }
6102
+ if (strategySchema.riskList &&
6103
+ strategySchema.riskList.length !== new Set(strategySchema.riskList).size) {
6104
+ throw new Error(`strategy schema validation failed: found duplicate riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
6105
+ }
6106
+ if (strategySchema.riskList?.some((value) => typeof value !== "string")) {
6107
+ throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
6108
+ }
5038
6109
  if (typeof strategySchema.interval !== "string") {
5039
6110
  throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
5040
6111
  }
@@ -5443,7 +6514,7 @@ class BacktestLogicPrivateService {
5443
6514
  });
5444
6515
  }
5445
6516
  // Check if strategy should stop before processing next frame
5446
- if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
6517
+ if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
5447
6518
  this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
5448
6519
  symbol,
5449
6520
  when: when.toISOString(),
@@ -5468,7 +6539,7 @@ class BacktestLogicPrivateService {
5468
6539
  continue;
5469
6540
  }
5470
6541
  // Check if strategy should stop when idle (no active signal)
5471
- if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
6542
+ if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName))) {
5472
6543
  this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
5473
6544
  symbol,
5474
6545
  when: when.toISOString(),
@@ -5570,7 +6641,7 @@ class BacktestLogicPrivateService {
5570
6641
  }
5571
6642
  yield backtestResult;
5572
6643
  // Check if strategy should stop after signal is closed
5573
- if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
6644
+ if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
5574
6645
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
5575
6646
  symbol,
5576
6647
  signalId: backtestResult.signal.id,
@@ -5663,7 +6734,7 @@ class BacktestLogicPrivateService {
5663
6734
  }
5664
6735
  yield backtestResult;
5665
6736
  // Check if strategy should stop after signal is closed
5666
- if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
6737
+ if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
5667
6738
  this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
5668
6739
  symbol,
5669
6740
  signalId: backtestResult.signal.id,
@@ -5806,7 +6877,7 @@ class LiveLogicPrivateService {
5806
6877
  previousEventTimestamp = currentTimestamp;
5807
6878
  // Check if strategy should stop when idle (no active signal)
5808
6879
  if (result.action === "idle") {
5809
- if (await functoolsKit.and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
6880
+ if (await functoolsKit.and(Promise.resolve(true), this.strategyCoreService.getStopped(false, symbol, this.methodContextService.context.strategyName))) {
5810
6881
  this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
5811
6882
  symbol,
5812
6883
  when: when.toISOString(),
@@ -5828,7 +6899,7 @@ class LiveLogicPrivateService {
5828
6899
  yield result;
5829
6900
  // Check if strategy should stop after signal is closed
5830
6901
  if (result.action === "closed") {
5831
- if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
6902
+ if (await this.strategyCoreService.getStopped(false, symbol, this.methodContextService.context.strategyName)) {
5832
6903
  this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
5833
6904
  symbol,
5834
6905
  signalId: result.signal.id,
@@ -6276,167 +7347,80 @@ class BacktestCommandService {
6276
7347
  }
6277
7348
  {
6278
7349
  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
- ];
7350
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
7351
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
7352
+ }
7353
+ return this.backtestLogicPublicService.run(symbol, context);
7354
+ };
7355
+ }
7356
+ }
7357
+
7358
+ const METHOD_NAME_RUN = "walkerCommandService run";
7359
+ /**
7360
+ * Global service providing access to walker functionality.
7361
+ *
7362
+ * Simple wrapper around WalkerLogicPublicService for dependency injection.
7363
+ * Used by public API exports.
7364
+ */
7365
+ class WalkerCommandService {
7366
+ constructor() {
7367
+ this.loggerService = inject(TYPES.loggerService);
7368
+ this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
7369
+ this.walkerSchemaService = inject(TYPES.walkerSchemaService);
7370
+ this.strategyValidationService = inject(TYPES.strategyValidationService);
7371
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
7372
+ this.frameValidationService = inject(TYPES.frameValidationService);
7373
+ this.walkerValidationService = inject(TYPES.walkerValidationService);
7374
+ this.strategySchemaService = inject(TYPES.strategySchemaService);
7375
+ this.riskValidationService = inject(TYPES.riskValidationService);
7376
+ /**
7377
+ * Runs walker comparison for a symbol with context propagation.
7378
+ *
7379
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
7380
+ * @param context - Walker context with strategies and metric
7381
+ */
7382
+ this.run = (symbol, context) => {
7383
+ this.loggerService.log(METHOD_NAME_RUN, {
7384
+ symbol,
7385
+ context,
7386
+ });
7387
+ {
7388
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
7389
+ this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
7390
+ this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
7391
+ }
7392
+ {
7393
+ const walkerSchema = this.walkerSchemaService.get(context.walkerName);
7394
+ for (const strategyName of walkerSchema.strategies) {
7395
+ const { riskName, riskList } = this.strategySchemaService.get(strategyName);
7396
+ this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
7397
+ riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
7398
+ riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
7399
+ }
7400
+ }
7401
+ return this.walkerLogicPublicService.run(symbol, context);
7402
+ };
7403
+ }
7404
+ }
7405
+
7406
+ /**
7407
+ * Checks if a value is unsafe for display (not a number, NaN, or Infinity).
7408
+ *
7409
+ * @param value - Value to check
7410
+ * @returns true if value is unsafe, false otherwise
7411
+ */
7412
+ function isUnsafe$3(value) {
7413
+ if (typeof value !== "number") {
7414
+ return true;
7415
+ }
7416
+ if (isNaN(value)) {
7417
+ return true;
7418
+ }
7419
+ if (!isFinite(value)) {
7420
+ return true;
7421
+ }
7422
+ return false;
7423
+ }
6440
7424
  /** Maximum number of signals to store in backtest reports */
6441
7425
  const MAX_EVENTS$6 = 250;
6442
7426
  /**
@@ -6530,9 +7514,10 @@ let ReportStorage$5 = class ReportStorage {
6530
7514
  * Generates markdown report with all closed signals for a strategy (View).
6531
7515
  *
6532
7516
  * @param strategyName - Strategy name
7517
+ * @param columns - Column configuration for formatting the table
6533
7518
  * @returns Markdown formatted report with all signals
6534
7519
  */
6535
- async getReport(strategyName) {
7520
+ async getReport(strategyName, columns = COLUMN_CONFIG.backtest_columns) {
6536
7521
  const stats = await this.getData();
6537
7522
  if (stats.totalSignals === 0) {
6538
7523
  return [
@@ -6541,10 +7526,15 @@ let ReportStorage$5 = class ReportStorage {
6541
7526
  "No signals closed yet."
6542
7527
  ].join("\n");
6543
7528
  }
6544
- const visibleColumns = columns$6.filter((col) => col.isVisible());
7529
+ const visibleColumns = [];
7530
+ for (const col of columns) {
7531
+ if (await col.isVisible()) {
7532
+ visibleColumns.push(col);
7533
+ }
7534
+ }
6545
7535
  const header = visibleColumns.map((col) => col.label);
6546
7536
  const separator = visibleColumns.map(() => "---");
6547
- const rows = this._signalList.map((closedSignal) => visibleColumns.map((col) => col.format(closedSignal)));
7537
+ const rows = await Promise.all(this._signalList.map(async (closedSignal, index) => Promise.all(visibleColumns.map((col) => col.format(closedSignal, index)))));
6548
7538
  const tableData = [header, separator, ...rows];
6549
7539
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6550
7540
  return [
@@ -6569,9 +7559,10 @@ let ReportStorage$5 = class ReportStorage {
6569
7559
  *
6570
7560
  * @param strategyName - Strategy name
6571
7561
  * @param path - Directory path to save report (default: "./dump/backtest")
7562
+ * @param columns - Column configuration for formatting the table
6572
7563
  */
6573
- async dump(strategyName, path$1 = "./dump/backtest") {
6574
- const markdown = await this.getReport(strategyName);
7564
+ async dump(strategyName, path$1 = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
7565
+ const markdown = await this.getReport(strategyName, columns);
6575
7566
  try {
6576
7567
  const dir = path.join(process.cwd(), path$1);
6577
7568
  await fs.mkdir(dir, { recursive: true });
@@ -6679,6 +7670,7 @@ class BacktestMarkdownService {
6679
7670
  *
6680
7671
  * @param symbol - Trading pair symbol
6681
7672
  * @param strategyName - Strategy name to generate report for
7673
+ * @param columns - Column configuration for formatting the table
6682
7674
  * @returns Markdown formatted report string with table of all closed signals
6683
7675
  *
6684
7676
  * @example
@@ -6688,13 +7680,13 @@ class BacktestMarkdownService {
6688
7680
  * console.log(markdown);
6689
7681
  * ```
6690
7682
  */
6691
- this.getReport = async (symbol, strategyName) => {
7683
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.backtest_columns) => {
6692
7684
  this.loggerService.log("backtestMarkdownService getReport", {
6693
7685
  symbol,
6694
7686
  strategyName,
6695
7687
  });
6696
7688
  const storage = this.getStorage(symbol, strategyName);
6697
- return storage.getReport(strategyName);
7689
+ return storage.getReport(strategyName, columns);
6698
7690
  };
6699
7691
  /**
6700
7692
  * Saves symbol-strategy report to disk.
@@ -6704,6 +7696,7 @@ class BacktestMarkdownService {
6704
7696
  * @param symbol - Trading pair symbol
6705
7697
  * @param strategyName - Strategy name to save report for
6706
7698
  * @param path - Directory path to save report (default: "./dump/backtest")
7699
+ * @param columns - Column configuration for formatting the table
6707
7700
  *
6708
7701
  * @example
6709
7702
  * ```typescript
@@ -6716,14 +7709,14 @@ class BacktestMarkdownService {
6716
7709
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
6717
7710
  * ```
6718
7711
  */
6719
- this.dump = async (symbol, strategyName, path = "./dump/backtest") => {
7712
+ this.dump = async (symbol, strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) => {
6720
7713
  this.loggerService.log("backtestMarkdownService dump", {
6721
7714
  symbol,
6722
7715
  strategyName,
6723
7716
  path,
6724
7717
  });
6725
7718
  const storage = this.getStorage(symbol, strategyName);
6726
- await storage.dump(strategyName, path);
7719
+ await storage.dump(strategyName, path, columns);
6727
7720
  };
6728
7721
  /**
6729
7722
  * Clears accumulated signal data from storage.
@@ -6791,104 +7784,6 @@ function isUnsafe$2(value) {
6791
7784
  }
6792
7785
  return false;
6793
7786
  }
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
7787
  /** Maximum number of events to store in live trading reports */
6893
7788
  const MAX_EVENTS$5 = 250;
6894
7789
  /**
@@ -7100,9 +7995,10 @@ let ReportStorage$4 = class ReportStorage {
7100
7995
  * Generates markdown report with all tick events for a strategy (View).
7101
7996
  *
7102
7997
  * @param strategyName - Strategy name
7998
+ * @param columns - Column configuration for formatting the table
7103
7999
  * @returns Markdown formatted report with all events
7104
8000
  */
7105
- async getReport(strategyName) {
8001
+ async getReport(strategyName, columns = COLUMN_CONFIG.live_columns) {
7106
8002
  const stats = await this.getData();
7107
8003
  if (stats.totalEvents === 0) {
7108
8004
  return [
@@ -7111,10 +8007,15 @@ let ReportStorage$4 = class ReportStorage {
7111
8007
  "No events recorded yet."
7112
8008
  ].join("\n");
7113
8009
  }
7114
- const visibleColumns = columns$5.filter((col) => col.isVisible());
8010
+ const visibleColumns = [];
8011
+ for (const col of columns) {
8012
+ if (await col.isVisible()) {
8013
+ visibleColumns.push(col);
8014
+ }
8015
+ }
7115
8016
  const header = visibleColumns.map((col) => col.label);
7116
8017
  const separator = visibleColumns.map(() => "---");
7117
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
8018
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
7118
8019
  const tableData = [header, separator, ...rows];
7119
8020
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
7120
8021
  return [
@@ -7139,9 +8040,10 @@ let ReportStorage$4 = class ReportStorage {
7139
8040
  *
7140
8041
  * @param strategyName - Strategy name
7141
8042
  * @param path - Directory path to save report (default: "./dump/live")
8043
+ * @param columns - Column configuration for formatting the table
7142
8044
  */
7143
- async dump(strategyName, path$1 = "./dump/live") {
7144
- const markdown = await this.getReport(strategyName);
8045
+ async dump(strategyName, path$1 = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
8046
+ const markdown = await this.getReport(strategyName, columns);
7145
8047
  try {
7146
8048
  const dir = path.join(process.cwd(), path$1);
7147
8049
  await fs.mkdir(dir, { recursive: true });
@@ -7262,6 +8164,7 @@ class LiveMarkdownService {
7262
8164
  *
7263
8165
  * @param symbol - Trading pair symbol
7264
8166
  * @param strategyName - Strategy name to generate report for
8167
+ * @param columns - Column configuration for formatting the table
7265
8168
  * @returns Markdown formatted report string with table of all events
7266
8169
  *
7267
8170
  * @example
@@ -7271,13 +8174,13 @@ class LiveMarkdownService {
7271
8174
  * console.log(markdown);
7272
8175
  * ```
7273
8176
  */
7274
- this.getReport = async (symbol, strategyName) => {
8177
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.live_columns) => {
7275
8178
  this.loggerService.log("liveMarkdownService getReport", {
7276
8179
  symbol,
7277
8180
  strategyName,
7278
8181
  });
7279
8182
  const storage = this.getStorage(symbol, strategyName);
7280
- return storage.getReport(strategyName);
8183
+ return storage.getReport(strategyName, columns);
7281
8184
  };
7282
8185
  /**
7283
8186
  * Saves symbol-strategy report to disk.
@@ -7287,6 +8190,7 @@ class LiveMarkdownService {
7287
8190
  * @param symbol - Trading pair symbol
7288
8191
  * @param strategyName - Strategy name to save report for
7289
8192
  * @param path - Directory path to save report (default: "./dump/live")
8193
+ * @param columns - Column configuration for formatting the table
7290
8194
  *
7291
8195
  * @example
7292
8196
  * ```typescript
@@ -7299,14 +8203,14 @@ class LiveMarkdownService {
7299
8203
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
7300
8204
  * ```
7301
8205
  */
7302
- this.dump = async (symbol, strategyName, path = "./dump/live") => {
8206
+ this.dump = async (symbol, strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) => {
7303
8207
  this.loggerService.log("liveMarkdownService dump", {
7304
8208
  symbol,
7305
8209
  strategyName,
7306
8210
  path,
7307
8211
  });
7308
8212
  const storage = this.getStorage(symbol, strategyName);
7309
- await storage.dump(strategyName, path);
8213
+ await storage.dump(strategyName, path, columns);
7310
8214
  };
7311
8215
  /**
7312
8216
  * Clears accumulated event data from storage.
@@ -7342,88 +8246,20 @@ class LiveMarkdownService {
7342
8246
  * Initializes the service by subscribing to live signal events.
7343
8247
  * Uses singleshot to ensure initialization happens only once.
7344
8248
  * Automatically called on first use.
7345
- *
7346
- * @example
7347
- * ```typescript
7348
- * const service = new LiveMarkdownService();
7349
- * await service.init(); // Subscribe to live events
7350
- * ```
7351
- */
7352
- this.init = functoolsKit.singleshot(async () => {
7353
- this.loggerService.log("liveMarkdownService init");
7354
- signalLiveEmitter.subscribe(this.tick);
7355
- });
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
- ];
8249
+ *
8250
+ * @example
8251
+ * ```typescript
8252
+ * const service = new LiveMarkdownService();
8253
+ * await service.init(); // Subscribe to live events
8254
+ * ```
8255
+ */
8256
+ this.init = functoolsKit.singleshot(async () => {
8257
+ this.loggerService.log("liveMarkdownService init");
8258
+ signalLiveEmitter.subscribe(this.tick);
8259
+ });
8260
+ }
8261
+ }
8262
+
7427
8263
  /** Maximum number of events to store in schedule reports */
7428
8264
  const MAX_EVENTS$4 = 250;
7429
8265
  /**
@@ -7568,9 +8404,10 @@ let ReportStorage$3 = class ReportStorage {
7568
8404
  * Generates markdown report with all scheduled events for a strategy (View).
7569
8405
  *
7570
8406
  * @param strategyName - Strategy name
8407
+ * @param columns - Column configuration for formatting the table
7571
8408
  * @returns Markdown formatted report with all events
7572
8409
  */
7573
- async getReport(strategyName) {
8410
+ async getReport(strategyName, columns = COLUMN_CONFIG.schedule_columns) {
7574
8411
  const stats = await this.getData();
7575
8412
  if (stats.totalEvents === 0) {
7576
8413
  return [
@@ -7579,10 +8416,15 @@ let ReportStorage$3 = class ReportStorage {
7579
8416
  "No scheduled signals recorded yet."
7580
8417
  ].join("\n");
7581
8418
  }
7582
- const visibleColumns = columns$4.filter((col) => col.isVisible());
8419
+ const visibleColumns = [];
8420
+ for (const col of columns) {
8421
+ if (await col.isVisible()) {
8422
+ visibleColumns.push(col);
8423
+ }
8424
+ }
7583
8425
  const header = visibleColumns.map((col) => col.label);
7584
8426
  const separator = visibleColumns.map(() => "---");
7585
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
8427
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
7586
8428
  const tableData = [header, separator, ...rows];
7587
8429
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7588
8430
  return [
@@ -7605,9 +8447,10 @@ let ReportStorage$3 = class ReportStorage {
7605
8447
  *
7606
8448
  * @param strategyName - Strategy name
7607
8449
  * @param path - Directory path to save report (default: "./dump/schedule")
8450
+ * @param columns - Column configuration for formatting the table
7608
8451
  */
7609
- async dump(strategyName, path$1 = "./dump/schedule") {
7610
- const markdown = await this.getReport(strategyName);
8452
+ async dump(strategyName, path$1 = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
8453
+ const markdown = await this.getReport(strategyName, columns);
7611
8454
  try {
7612
8455
  const dir = path.join(process.cwd(), path$1);
7613
8456
  await fs.mkdir(dir, { recursive: true });
@@ -7713,6 +8556,7 @@ class ScheduleMarkdownService {
7713
8556
  *
7714
8557
  * @param symbol - Trading pair symbol
7715
8558
  * @param strategyName - Strategy name to generate report for
8559
+ * @param columns - Column configuration for formatting the table
7716
8560
  * @returns Markdown formatted report string with table of all events
7717
8561
  *
7718
8562
  * @example
@@ -7722,13 +8566,13 @@ class ScheduleMarkdownService {
7722
8566
  * console.log(markdown);
7723
8567
  * ```
7724
8568
  */
7725
- this.getReport = async (symbol, strategyName) => {
8569
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.schedule_columns) => {
7726
8570
  this.loggerService.log("scheduleMarkdownService getReport", {
7727
8571
  symbol,
7728
8572
  strategyName,
7729
8573
  });
7730
8574
  const storage = this.getStorage(symbol, strategyName);
7731
- return storage.getReport(strategyName);
8575
+ return storage.getReport(strategyName, columns);
7732
8576
  };
7733
8577
  /**
7734
8578
  * Saves symbol-strategy report to disk.
@@ -7738,6 +8582,7 @@ class ScheduleMarkdownService {
7738
8582
  * @param symbol - Trading pair symbol
7739
8583
  * @param strategyName - Strategy name to save report for
7740
8584
  * @param path - Directory path to save report (default: "./dump/schedule")
8585
+ * @param columns - Column configuration for formatting the table
7741
8586
  *
7742
8587
  * @example
7743
8588
  * ```typescript
@@ -7750,14 +8595,14 @@ class ScheduleMarkdownService {
7750
8595
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
7751
8596
  * ```
7752
8597
  */
7753
- this.dump = async (symbol, strategyName, path = "./dump/schedule") => {
8598
+ this.dump = async (symbol, strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) => {
7754
8599
  this.loggerService.log("scheduleMarkdownService dump", {
7755
8600
  symbol,
7756
8601
  strategyName,
7757
8602
  path,
7758
8603
  });
7759
8604
  const storage = this.getStorage(symbol, strategyName);
7760
- await storage.dump(strategyName, path);
8605
+ await storage.dump(strategyName, path, columns);
7761
8606
  };
7762
8607
  /**
7763
8608
  * Clears accumulated event data from storage.
@@ -7816,86 +8661,6 @@ function percentile(sortedArray, p) {
7816
8661
  const index = Math.ceil((sortedArray.length * p) / 100) - 1;
7817
8662
  return sortedArray[Math.max(0, index)];
7818
8663
  }
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
8664
  /** Maximum number of performance events to store per strategy */
7900
8665
  const MAX_EVENTS$3 = 10000;
7901
8666
  /**
@@ -7998,9 +8763,10 @@ class PerformanceStorage {
7998
8763
  * Generates markdown report with performance statistics.
7999
8764
  *
8000
8765
  * @param strategyName - Strategy name
8766
+ * @param columns - Column configuration for formatting the table
8001
8767
  * @returns Markdown formatted report
8002
8768
  */
8003
- async getReport(strategyName) {
8769
+ async getReport(strategyName, columns = COLUMN_CONFIG.performance_columns) {
8004
8770
  const stats = await this.getData(strategyName);
8005
8771
  if (stats.totalEvents === 0) {
8006
8772
  return [
@@ -8012,10 +8778,15 @@ class PerformanceStorage {
8012
8778
  // Sort metrics by total duration (descending) to show bottlenecks first
8013
8779
  const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
8014
8780
  // Generate summary table using Column interface
8015
- const visibleColumns = columns$3.filter((col) => col.isVisible());
8781
+ const visibleColumns = [];
8782
+ for (const col of columns) {
8783
+ if (await col.isVisible()) {
8784
+ visibleColumns.push(col);
8785
+ }
8786
+ }
8016
8787
  const header = visibleColumns.map((col) => col.label);
8017
8788
  const separator = visibleColumns.map(() => "---");
8018
- const rows = sortedMetrics.map((metric) => visibleColumns.map((col) => col.format(metric)));
8789
+ const rows = await Promise.all(sortedMetrics.map(async (metric, index) => Promise.all(visibleColumns.map((col) => col.format(metric, index)))));
8019
8790
  const tableData = [header, separator, ...rows];
8020
8791
  const summaryTable = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8021
8792
  // Calculate percentage of total time for each metric
@@ -8046,9 +8817,10 @@ class PerformanceStorage {
8046
8817
  *
8047
8818
  * @param strategyName - Strategy name
8048
8819
  * @param path - Directory path to save report
8820
+ * @param columns - Column configuration for formatting the table
8049
8821
  */
8050
- async dump(strategyName, path$1 = "./dump/performance") {
8051
- const markdown = await this.getReport(strategyName);
8822
+ async dump(strategyName, path$1 = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
8823
+ const markdown = await this.getReport(strategyName, columns);
8052
8824
  try {
8053
8825
  const dir = path.join(process.cwd(), path$1);
8054
8826
  await fs.mkdir(dir, { recursive: true });
@@ -8141,6 +8913,7 @@ class PerformanceMarkdownService {
8141
8913
  *
8142
8914
  * @param symbol - Trading pair symbol
8143
8915
  * @param strategyName - Strategy name to generate report for
8916
+ * @param columns - Column configuration for formatting the table
8144
8917
  * @returns Markdown formatted report string
8145
8918
  *
8146
8919
  * @example
@@ -8149,13 +8922,13 @@ class PerformanceMarkdownService {
8149
8922
  * console.log(markdown);
8150
8923
  * ```
8151
8924
  */
8152
- this.getReport = async (symbol, strategyName) => {
8925
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.performance_columns) => {
8153
8926
  this.loggerService.log("performanceMarkdownService getReport", {
8154
8927
  symbol,
8155
8928
  strategyName,
8156
8929
  });
8157
8930
  const storage = this.getStorage(symbol, strategyName);
8158
- return storage.getReport(strategyName);
8931
+ return storage.getReport(strategyName, columns);
8159
8932
  };
8160
8933
  /**
8161
8934
  * Saves performance report to disk.
@@ -8163,6 +8936,7 @@ class PerformanceMarkdownService {
8163
8936
  * @param symbol - Trading pair symbol
8164
8937
  * @param strategyName - Strategy name to save report for
8165
8938
  * @param path - Directory path to save report
8939
+ * @param columns - Column configuration for formatting the table
8166
8940
  *
8167
8941
  * @example
8168
8942
  * ```typescript
@@ -8173,14 +8947,14 @@ class PerformanceMarkdownService {
8173
8947
  * await performanceService.dump("BTCUSDT", "my-strategy", "./custom/path");
8174
8948
  * ```
8175
8949
  */
8176
- this.dump = async (symbol, strategyName, path = "./dump/performance") => {
8950
+ this.dump = async (symbol, strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) => {
8177
8951
  this.loggerService.log("performanceMarkdownService dump", {
8178
8952
  symbol,
8179
8953
  strategyName,
8180
8954
  path,
8181
8955
  });
8182
8956
  const storage = this.getStorage(symbol, strategyName);
8183
- await storage.dump(strategyName, path);
8957
+ await storage.dump(strategyName, path, columns);
8184
8958
  };
8185
8959
  /**
8186
8960
  * Clears accumulated performance data from storage.
@@ -8239,135 +9013,6 @@ function formatMetric(value) {
8239
9013
  }
8240
9014
  return value.toFixed(2);
8241
9015
  }
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
9016
  /**
8372
9017
  * Storage class for accumulating walker results.
8373
9018
  * Maintains a list of all strategy results and provides methods to generate reports.
@@ -8399,7 +9044,7 @@ let ReportStorage$2 = class ReportStorage {
8399
9044
  this._strategyResults.unshift({
8400
9045
  strategyName: data.strategyName,
8401
9046
  stats: data.stats,
8402
- metricValue: data.metricValue,
9047
+ metricValue: isUnsafe$1(data.metricValue) ? null : data.metricValue,
8403
9048
  });
8404
9049
  }
8405
9050
  /**
@@ -8432,11 +9077,11 @@ let ReportStorage$2 = class ReportStorage {
8432
9077
  * Generates comparison table for top N strategies (View).
8433
9078
  * Sorts strategies by metric value and formats as markdown table.
8434
9079
  *
8435
- * @param metric - Metric being optimized
8436
9080
  * @param topN - Number of top strategies to include (default: 10)
9081
+ * @param columns - Column configuration for formatting the strategy comparison table
8437
9082
  * @returns Markdown formatted comparison table
8438
9083
  */
8439
- getComparisonTable(metric, topN = 10) {
9084
+ async getComparisonTable(topN = 10, columns = COLUMN_CONFIG.walker_strategy_columns) {
8440
9085
  if (this._strategyResults.length === 0) {
8441
9086
  return "No strategy results available.";
8442
9087
  }
@@ -8449,13 +9094,17 @@ let ReportStorage$2 = class ReportStorage {
8449
9094
  // Take top N strategies
8450
9095
  const topStrategies = sortedResults.slice(0, topN);
8451
9096
  // Get columns configuration
8452
- const columns = createStrategyColumns(metric);
8453
- const visibleColumns = columns.filter((col) => col.isVisible());
9097
+ const visibleColumns = [];
9098
+ for (const col of columns) {
9099
+ if (await col.isVisible()) {
9100
+ visibleColumns.push(col);
9101
+ }
9102
+ }
8454
9103
  // Build table header
8455
9104
  const header = visibleColumns.map((col) => col.label);
8456
9105
  const separator = visibleColumns.map(() => "---");
8457
9106
  // Build table rows
8458
- const rows = topStrategies.map((result, index) => visibleColumns.map((col) => col.format(result, index)));
9107
+ const rows = await Promise.all(topStrategies.map(async (result, index) => Promise.all(visibleColumns.map((col) => col.format(result, index)))));
8459
9108
  const tableData = [header, separator, ...rows];
8460
9109
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8461
9110
  }
@@ -8463,9 +9112,10 @@ let ReportStorage$2 = class ReportStorage {
8463
9112
  * Generates PNL table showing all closed signals across all strategies (View).
8464
9113
  * Collects all signals from all strategies and formats as markdown table.
8465
9114
  *
9115
+ * @param columns - Column configuration for formatting the PNL table
8466
9116
  * @returns Markdown formatted PNL table
8467
9117
  */
8468
- getPnlTable() {
9118
+ async getPnlTable(columns = COLUMN_CONFIG.walker_pnl_columns) {
8469
9119
  if (this._strategyResults.length === 0) {
8470
9120
  return "No strategy results available.";
8471
9121
  }
@@ -8489,11 +9139,16 @@ let ReportStorage$2 = class ReportStorage {
8489
9139
  return "No closed signals available.";
8490
9140
  }
8491
9141
  // Build table header
8492
- const visibleColumns = pnlColumns.filter((col) => col.isVisible());
9142
+ const visibleColumns = [];
9143
+ for (const col of columns) {
9144
+ if (await col.isVisible()) {
9145
+ visibleColumns.push(col);
9146
+ }
9147
+ }
8493
9148
  const header = visibleColumns.map((col) => col.label);
8494
9149
  const separator = visibleColumns.map(() => "---");
8495
9150
  // Build table rows
8496
- const rows = allSignals.map((signal) => visibleColumns.map((col) => col.format(signal)));
9151
+ const rows = await Promise.all(allSignals.map(async (signal, index) => Promise.all(visibleColumns.map((col) => col.format(signal, index)))));
8497
9152
  const tableData = [header, separator, ...rows];
8498
9153
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8499
9154
  }
@@ -8504,9 +9159,11 @@ let ReportStorage$2 = class ReportStorage {
8504
9159
  * @param symbol - Trading symbol
8505
9160
  * @param metric - Metric being optimized
8506
9161
  * @param context - Context with exchangeName and frameName
9162
+ * @param strategyColumns - Column configuration for strategy comparison table
9163
+ * @param pnlColumns - Column configuration for PNL table
8507
9164
  * @returns Markdown formatted report with all results
8508
9165
  */
8509
- async getReport(symbol, metric, context) {
9166
+ async getReport(symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
8510
9167
  const results = await this.getData(symbol, metric, context);
8511
9168
  // Get total signals for best strategy
8512
9169
  const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
@@ -8526,11 +9183,11 @@ let ReportStorage$2 = class ReportStorage {
8526
9183
  "",
8527
9184
  "## Top Strategies Comparison",
8528
9185
  "",
8529
- this.getComparisonTable(metric, 10),
9186
+ await this.getComparisonTable(10, strategyColumns),
8530
9187
  "",
8531
9188
  "## All Signals (PNL Table)",
8532
9189
  "",
8533
- this.getPnlTable(),
9190
+ await this.getPnlTable(pnlColumns),
8534
9191
  "",
8535
9192
  "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
8536
9193
  ].join("\n");
@@ -8542,9 +9199,11 @@ let ReportStorage$2 = class ReportStorage {
8542
9199
  * @param metric - Metric being optimized
8543
9200
  * @param context - Context with exchangeName and frameName
8544
9201
  * @param path - Directory path to save report (default: "./dump/walker")
9202
+ * @param strategyColumns - Column configuration for strategy comparison table
9203
+ * @param pnlColumns - Column configuration for PNL table
8545
9204
  */
8546
- async dump(symbol, metric, context, path$1 = "./dump/walker") {
8547
- const markdown = await this.getReport(symbol, metric, context);
9205
+ async dump(symbol, metric, context, path$1 = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
9206
+ const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
8548
9207
  try {
8549
9208
  const dir = path.join(process.cwd(), path$1);
8550
9209
  await fs.mkdir(dir, { recursive: true });
@@ -8637,6 +9296,8 @@ class WalkerMarkdownService {
8637
9296
  * @param symbol - Trading symbol
8638
9297
  * @param metric - Metric being optimized
8639
9298
  * @param context - Context with exchangeName and frameName
9299
+ * @param strategyColumns - Column configuration for strategy comparison table
9300
+ * @param pnlColumns - Column configuration for PNL table
8640
9301
  * @returns Markdown formatted report string
8641
9302
  *
8642
9303
  * @example
@@ -8646,7 +9307,7 @@ class WalkerMarkdownService {
8646
9307
  * console.log(markdown);
8647
9308
  * ```
8648
9309
  */
8649
- this.getReport = async (walkerName, symbol, metric, context) => {
9310
+ this.getReport = async (walkerName, symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
8650
9311
  this.loggerService.log("walkerMarkdownService getReport", {
8651
9312
  walkerName,
8652
9313
  symbol,
@@ -8654,7 +9315,7 @@ class WalkerMarkdownService {
8654
9315
  context,
8655
9316
  });
8656
9317
  const storage = this.getStorage(walkerName);
8657
- return storage.getReport(symbol, metric, context);
9318
+ return storage.getReport(symbol, metric, context, strategyColumns, pnlColumns);
8658
9319
  };
8659
9320
  /**
8660
9321
  * Saves walker report to disk.
@@ -8666,6 +9327,8 @@ class WalkerMarkdownService {
8666
9327
  * @param metric - Metric being optimized
8667
9328
  * @param context - Context with exchangeName and frameName
8668
9329
  * @param path - Directory path to save report (default: "./dump/walker")
9330
+ * @param strategyColumns - Column configuration for strategy comparison table
9331
+ * @param pnlColumns - Column configuration for PNL table
8669
9332
  *
8670
9333
  * @example
8671
9334
  * ```typescript
@@ -8678,7 +9341,7 @@ class WalkerMarkdownService {
8678
9341
  * await service.dump("my-walker", "BTCUSDT", "sharpeRatio", { exchangeName: "binance", frameName: "1d" }, "./custom/path");
8679
9342
  * ```
8680
9343
  */
8681
- this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker") => {
9344
+ this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
8682
9345
  this.loggerService.log("walkerMarkdownService dump", {
8683
9346
  walkerName,
8684
9347
  symbol,
@@ -8687,7 +9350,7 @@ class WalkerMarkdownService {
8687
9350
  path,
8688
9351
  });
8689
9352
  const storage = this.getStorage(walkerName);
8690
- await storage.dump(symbol, metric, context, path);
9353
+ await storage.dump(symbol, metric, context, path, strategyColumns, pnlColumns);
8691
9354
  };
8692
9355
  /**
8693
9356
  * Clears accumulated result data from storage.
@@ -8753,80 +9416,6 @@ function isUnsafe(value) {
8753
9416
  }
8754
9417
  return false;
8755
9418
  }
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
- ];
8830
9419
  /** Maximum number of signals to store per symbol in heatmap reports */
8831
9420
  const MAX_EVENTS$2 = 250;
8832
9421
  /**
@@ -9061,9 +9650,10 @@ class HeatmapStorage {
9061
9650
  * Generates markdown report with portfolio heatmap table (View).
9062
9651
  *
9063
9652
  * @param strategyName - Strategy name for report title
9653
+ * @param columns - Column configuration for formatting the table
9064
9654
  * @returns Promise resolving to markdown formatted report string
9065
9655
  */
9066
- async getReport(strategyName) {
9656
+ async getReport(strategyName, columns = COLUMN_CONFIG.heat_columns) {
9067
9657
  const data = await this.getData();
9068
9658
  if (data.symbols.length === 0) {
9069
9659
  return [
@@ -9072,10 +9662,15 @@ class HeatmapStorage {
9072
9662
  "*No data available*"
9073
9663
  ].join("\n");
9074
9664
  }
9075
- const visibleColumns = columns$2.filter((col) => col.isVisible());
9665
+ const visibleColumns = [];
9666
+ for (const col of columns) {
9667
+ if (await col.isVisible()) {
9668
+ visibleColumns.push(col);
9669
+ }
9670
+ }
9076
9671
  const header = visibleColumns.map((col) => col.label);
9077
9672
  const separator = visibleColumns.map(() => "---");
9078
- const rows = data.symbols.map((row) => visibleColumns.map((col) => col.format(row)));
9673
+ const rows = await Promise.all(data.symbols.map(async (row, index) => Promise.all(visibleColumns.map((col) => col.format(row, index)))));
9079
9674
  const tableData = [header, separator, ...rows];
9080
9675
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
9081
9676
  return [
@@ -9091,9 +9686,10 @@ class HeatmapStorage {
9091
9686
  *
9092
9687
  * @param strategyName - Strategy name for filename
9093
9688
  * @param path - Directory path to save report (default: "./dump/heatmap")
9689
+ * @param columns - Column configuration for formatting the table
9094
9690
  */
9095
- async dump(strategyName, path$1 = "./dump/heatmap") {
9096
- const markdown = await this.getReport(strategyName);
9691
+ async dump(strategyName, path$1 = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
9692
+ const markdown = await this.getReport(strategyName, columns);
9097
9693
  try {
9098
9694
  const dir = path.join(process.cwd(), path$1);
9099
9695
  await fs.mkdir(dir, { recursive: true });
@@ -9190,6 +9786,7 @@ class HeatMarkdownService {
9190
9786
  * Generates markdown report with portfolio heatmap table for a strategy.
9191
9787
  *
9192
9788
  * @param strategyName - Strategy name to generate heatmap report for
9789
+ * @param columns - Column configuration for formatting the table
9193
9790
  * @returns Promise resolving to markdown formatted report string
9194
9791
  *
9195
9792
  * @example
@@ -9209,12 +9806,12 @@ class HeatMarkdownService {
9209
9806
  * // ...
9210
9807
  * ```
9211
9808
  */
9212
- this.getReport = async (strategyName) => {
9809
+ this.getReport = async (strategyName, columns = COLUMN_CONFIG.heat_columns) => {
9213
9810
  this.loggerService.log(HEATMAP_METHOD_NAME_GET_REPORT, {
9214
9811
  strategyName,
9215
9812
  });
9216
9813
  const storage = this.getStorage(strategyName);
9217
- return storage.getReport(strategyName);
9814
+ return storage.getReport(strategyName, columns);
9218
9815
  };
9219
9816
  /**
9220
9817
  * Saves heatmap report to disk for a strategy.
@@ -9224,6 +9821,7 @@ class HeatMarkdownService {
9224
9821
  *
9225
9822
  * @param strategyName - Strategy name to save heatmap report for
9226
9823
  * @param path - Optional directory path to save report (default: "./dump/heatmap")
9824
+ * @param columns - Column configuration for formatting the table
9227
9825
  *
9228
9826
  * @example
9229
9827
  * ```typescript
@@ -9236,13 +9834,13 @@ class HeatMarkdownService {
9236
9834
  * await service.dump("my-strategy", "./reports");
9237
9835
  * ```
9238
9836
  */
9239
- this.dump = async (strategyName, path = "./dump/heatmap") => {
9837
+ this.dump = async (strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) => {
9240
9838
  this.loggerService.log(HEATMAP_METHOD_NAME_DUMP, {
9241
9839
  strategyName,
9242
9840
  path,
9243
9841
  });
9244
9842
  const storage = this.getStorage(strategyName);
9245
- await storage.dump(strategyName, path);
9843
+ await storage.dump(strategyName, path, columns);
9246
9844
  };
9247
9845
  /**
9248
9846
  * Clears accumulated heatmap data from storage.
@@ -11096,6 +11694,9 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
11096
11694
  if (self._states === NEED_FETCH) {
11097
11695
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
11098
11696
  }
11697
+ if (data.id !== self.params.signalId) {
11698
+ throw new Error(`Signal ID mismatch: expected ${self.params.signalId}, got ${data.id}`);
11699
+ }
11099
11700
  let state = self._states.get(data.id);
11100
11701
  if (!state) {
11101
11702
  state = {
@@ -11120,7 +11721,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
11120
11721
  }
11121
11722
  }
11122
11723
  if (shouldPersist) {
11123
- await self._persistState(symbol, backtest);
11724
+ await self._persistState(symbol, data.strategyName);
11124
11725
  }
11125
11726
  };
11126
11727
  /**
@@ -11142,6 +11743,9 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
11142
11743
  if (self._states === NEED_FETCH) {
11143
11744
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
11144
11745
  }
11746
+ if (data.id !== self.params.signalId) {
11747
+ throw new Error(`Signal ID mismatch: expected ${self.params.signalId}, got ${data.id}`);
11748
+ }
11145
11749
  let state = self._states.get(data.id);
11146
11750
  if (!state) {
11147
11751
  state = {
@@ -11167,7 +11771,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
11167
11771
  }
11168
11772
  }
11169
11773
  if (shouldPersist) {
11170
- await self._persistState(symbol, backtest);
11774
+ await self._persistState(symbol, data.strategyName);
11171
11775
  }
11172
11776
  };
11173
11777
  /**
@@ -11176,15 +11780,29 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
11176
11780
  * Loads persisted partial state from disk and restores in-memory Maps.
11177
11781
  * Converts serialized arrays back to Sets for O(1) lookups.
11178
11782
  *
11783
+ * ONLY runs in LIVE mode (backtest=false). In backtest mode, state is not persisted.
11784
+ *
11179
11785
  * @param symbol - Trading pair symbol
11786
+ * @param strategyName - Strategy identifier
11787
+ * @param backtest - True if backtest mode, false if live mode
11180
11788
  * @param self - ClientPartial instance reference
11181
11789
  */
11182
- const WAIT_FOR_INIT_FN = async (symbol, self) => {
11183
- self.params.logger.debug("ClientPartial waitForInit", { symbol });
11184
- if (self._states === NEED_FETCH) {
11185
- throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
11790
+ const WAIT_FOR_INIT_FN = async (symbol, strategyName, self) => {
11791
+ self.params.logger.debug("ClientPartial waitForInit", {
11792
+ symbol,
11793
+ strategyName,
11794
+ backtest: self.params.backtest
11795
+ });
11796
+ if (self._states !== NEED_FETCH) {
11797
+ throw new Error("ClientPartial WAIT_FOR_INIT_FN should be called once!");
11798
+ }
11799
+ self._states = new Map();
11800
+ // Skip persistence in backtest mode
11801
+ if (self.params.backtest) {
11802
+ self.params.logger.debug("ClientPartial waitForInit: skipping persist read in backtest mode");
11803
+ return;
11186
11804
  }
11187
- const partialData = await PersistPartialAdapter.readPartialData(symbol);
11805
+ const partialData = await PersistPartialAdapter.readPartialData(symbol, strategyName);
11188
11806
  for (const [signalId, data] of Object.entries(partialData)) {
11189
11807
  const state = {
11190
11808
  profitLevels: new Set(data.profitLevels),
@@ -11194,6 +11812,7 @@ const WAIT_FOR_INIT_FN = async (symbol, self) => {
11194
11812
  }
11195
11813
  self.params.logger.info("ClientPartial restored state", {
11196
11814
  symbol,
11815
+ strategyName,
11197
11816
  signalCount: Object.keys(partialData).length,
11198
11817
  });
11199
11818
  };
@@ -11272,23 +11891,24 @@ class ClientPartial {
11272
11891
  /**
11273
11892
  * Initializes partial state by loading from disk.
11274
11893
  *
11275
- * Uses singleshot pattern to ensure initialization happens exactly once per symbol.
11894
+ * Uses singleshot pattern to ensure initialization happens exactly once per symbol:strategyName.
11276
11895
  * Reads persisted state from PersistPartialAdapter and restores to _states Map.
11277
11896
  *
11278
11897
  * Must be called before profit()/loss()/clear() methods.
11279
11898
  *
11280
11899
  * @param symbol - Trading pair symbol
11900
+ * @param strategyName - Strategy identifier
11901
+ * @param backtest - True if backtest mode, false if live mode
11281
11902
  * @returns Promise that resolves when initialization is complete
11282
11903
  *
11283
11904
  * @example
11284
11905
  * ```typescript
11285
11906
  * const partial = new ClientPartial(params);
11286
- * await partial.waitForInit("BTCUSDT"); // Load persisted state
11907
+ * await partial.waitForInit("BTCUSDT", "my-strategy", false); // Load persisted state (live mode)
11287
11908
  * // Now profit()/loss() can be called
11288
11909
  * ```
11289
11910
  */
11290
- this.waitForInit = functoolsKit.singleshot(async (symbol) => await WAIT_FOR_INIT_FN(symbol, this));
11291
- this._states = new Map();
11911
+ this.waitForInit = functoolsKit.singleshot(async (symbol, strategyName) => await WAIT_FOR_INIT_FN(symbol, strategyName, this));
11292
11912
  }
11293
11913
  /**
11294
11914
  * Persists current partial state to disk.
@@ -11301,13 +11921,15 @@ class ClientPartial {
11301
11921
  * Uses atomic file writes via PersistPartialAdapter.
11302
11922
  *
11303
11923
  * @param symbol - Trading pair symbol
11924
+ * @param strategyName - Strategy identifier
11925
+ * @param backtest - True if backtest mode
11304
11926
  * @returns Promise that resolves when persistence is complete
11305
11927
  */
11306
- async _persistState(symbol, backtest) {
11307
- if (backtest) {
11928
+ async _persistState(symbol, strategyName) {
11929
+ if (this.params.backtest) {
11308
11930
  return;
11309
11931
  }
11310
- this.params.logger.debug("ClientPartial persistState", { symbol });
11932
+ this.params.logger.debug("ClientPartial persistState", { symbol, strategyName });
11311
11933
  if (this._states === NEED_FETCH) {
11312
11934
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
11313
11935
  }
@@ -11318,7 +11940,7 @@ class ClientPartial {
11318
11940
  lossLevels: Array.from(state.lossLevels),
11319
11941
  };
11320
11942
  }
11321
- await PersistPartialAdapter.writePartialData(partialData, symbol);
11943
+ await PersistPartialAdapter.writePartialData(partialData, symbol, strategyName);
11322
11944
  }
11323
11945
  /**
11324
11946
  * Processes profit state and emits events for newly reached profit levels.
@@ -11439,8 +12061,11 @@ class ClientPartial {
11439
12061
  if (this._states === NEED_FETCH) {
11440
12062
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
11441
12063
  }
12064
+ if (data.id !== this.params.signalId) {
12065
+ throw new Error(`Signal ID mismatch: expected ${this.params.signalId}, got ${data.id}`);
12066
+ }
11442
12067
  this._states.delete(data.id);
11443
- await this._persistState(symbol, backtest);
12068
+ await this._persistState(symbol, data.strategyName);
11444
12069
  }
11445
12070
  }
11446
12071
 
@@ -11535,15 +12160,17 @@ class PartialConnectionService {
11535
12160
  /**
11536
12161
  * Memoized factory function for ClientPartial instances.
11537
12162
  *
11538
- * Creates one ClientPartial per signal ID with configured callbacks.
12163
+ * Creates one ClientPartial per signal ID and backtest mode with configured callbacks.
11539
12164
  * Instances are cached until clear() is called.
11540
12165
  *
11541
- * Key format: signalId
12166
+ * Key format: "signalId:backtest" or "signalId:live"
11542
12167
  * Value: ClientPartial instance with logger and event emitters
11543
12168
  */
11544
- this.getPartial = functoolsKit.memoize(([signalId]) => `${signalId}`, () => {
12169
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => `${signalId}:${backtest ? "backtest" : "live"}`, (signalId, backtest) => {
11545
12170
  return new ClientPartial({
12171
+ signalId,
11546
12172
  logger: this.loggerService,
12173
+ backtest,
11547
12174
  onProfit: COMMIT_PROFIT_FN,
11548
12175
  onLoss: COMMIT_LOSS_FN,
11549
12176
  });
@@ -11571,8 +12198,8 @@ class PartialConnectionService {
11571
12198
  backtest,
11572
12199
  when,
11573
12200
  });
11574
- const partial = this.getPartial(data.id);
11575
- await partial.waitForInit(symbol);
12201
+ const partial = this.getPartial(data.id, backtest);
12202
+ await partial.waitForInit(symbol, data.strategyName);
11576
12203
  return await partial.profit(symbol, data, currentPrice, revenuePercent, backtest, when);
11577
12204
  };
11578
12205
  /**
@@ -11598,8 +12225,8 @@ class PartialConnectionService {
11598
12225
  backtest,
11599
12226
  when,
11600
12227
  });
11601
- const partial = this.getPartial(data.id);
11602
- await partial.waitForInit(symbol);
12228
+ const partial = this.getPartial(data.id, backtest);
12229
+ await partial.waitForInit(symbol, data.strategyName);
11603
12230
  return await partial.loss(symbol, data, currentPrice, lossPercent, backtest, when);
11604
12231
  };
11605
12232
  /**
@@ -11620,75 +12247,21 @@ class PartialConnectionService {
11620
12247
  * @returns Promise that resolves when clear is complete
11621
12248
  */
11622
12249
  this.clear = async (symbol, data, priceClose, backtest) => {
11623
- this.loggerService.log("partialConnectionService profit", {
12250
+ this.loggerService.log("partialConnectionService clear", {
11624
12251
  symbol,
11625
12252
  data,
11626
12253
  priceClose,
12254
+ backtest,
11627
12255
  });
11628
- const partial = this.getPartial(data.id);
11629
- await partial.waitForInit(symbol);
12256
+ const partial = this.getPartial(data.id, backtest);
12257
+ await partial.waitForInit(symbol, data.strategyName);
11630
12258
  await partial.clear(symbol, data, priceClose, backtest);
11631
- this.getPartial.clear(data.id);
12259
+ const key = `${data.id}:${backtest ? "backtest" : "live"}`;
12260
+ this.getPartial.clear(key);
11632
12261
  };
11633
12262
  }
11634
12263
  }
11635
12264
 
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
12265
  /** Maximum number of events to store in partial reports */
11693
12266
  const MAX_EVENTS$1 = 250;
11694
12267
  /**
@@ -11778,9 +12351,10 @@ let ReportStorage$1 = class ReportStorage {
11778
12351
  *
11779
12352
  * @param symbol - Trading pair symbol
11780
12353
  * @param strategyName - Strategy name
12354
+ * @param columns - Column configuration for formatting the table
11781
12355
  * @returns Markdown formatted report with all events
11782
12356
  */
11783
- async getReport(symbol, strategyName) {
12357
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) {
11784
12358
  const stats = await this.getData();
11785
12359
  if (stats.totalEvents === 0) {
11786
12360
  return [
@@ -11789,10 +12363,15 @@ let ReportStorage$1 = class ReportStorage {
11789
12363
  "No partial profit/loss events recorded yet."
11790
12364
  ].join("\n");
11791
12365
  }
11792
- const visibleColumns = columns$1.filter((col) => col.isVisible());
12366
+ const visibleColumns = [];
12367
+ for (const col of columns) {
12368
+ if (await col.isVisible()) {
12369
+ visibleColumns.push(col);
12370
+ }
12371
+ }
11793
12372
  const header = visibleColumns.map((col) => col.label);
11794
12373
  const separator = visibleColumns.map(() => "---");
11795
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
12374
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
11796
12375
  const tableData = [header, separator, ...rows];
11797
12376
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
11798
12377
  return [
@@ -11811,9 +12390,10 @@ let ReportStorage$1 = class ReportStorage {
11811
12390
  * @param symbol - Trading pair symbol
11812
12391
  * @param strategyName - Strategy name
11813
12392
  * @param path - Directory path to save report (default: "./dump/partial")
12393
+ * @param columns - Column configuration for formatting the table
11814
12394
  */
11815
- async dump(symbol, strategyName, path$1 = "./dump/partial") {
11816
- const markdown = await this.getReport(symbol, strategyName);
12395
+ async dump(symbol, strategyName, path$1 = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
12396
+ const markdown = await this.getReport(symbol, strategyName, columns);
11817
12397
  try {
11818
12398
  const dir = path.join(process.cwd(), path$1);
11819
12399
  await fs.mkdir(dir, { recursive: true });
@@ -11924,6 +12504,7 @@ class PartialMarkdownService {
11924
12504
  *
11925
12505
  * @param symbol - Trading pair symbol to generate report for
11926
12506
  * @param strategyName - Strategy name to generate report for
12507
+ * @param columns - Column configuration for formatting the table
11927
12508
  * @returns Markdown formatted report string with table of all events
11928
12509
  *
11929
12510
  * @example
@@ -11933,13 +12514,13 @@ class PartialMarkdownService {
11933
12514
  * console.log(markdown);
11934
12515
  * ```
11935
12516
  */
11936
- this.getReport = async (symbol, strategyName) => {
12517
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) => {
11937
12518
  this.loggerService.log("partialMarkdownService getReport", {
11938
12519
  symbol,
11939
12520
  strategyName,
11940
12521
  });
11941
12522
  const storage = this.getStorage(symbol, strategyName);
11942
- return storage.getReport(symbol, strategyName);
12523
+ return storage.getReport(symbol, strategyName, columns);
11943
12524
  };
11944
12525
  /**
11945
12526
  * Saves symbol-strategy report to disk.
@@ -11949,6 +12530,7 @@ class PartialMarkdownService {
11949
12530
  * @param symbol - Trading pair symbol to save report for
11950
12531
  * @param strategyName - Strategy name to save report for
11951
12532
  * @param path - Directory path to save report (default: "./dump/partial")
12533
+ * @param columns - Column configuration for formatting the table
11952
12534
  *
11953
12535
  * @example
11954
12536
  * ```typescript
@@ -11961,14 +12543,14 @@ class PartialMarkdownService {
11961
12543
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
11962
12544
  * ```
11963
12545
  */
11964
- this.dump = async (symbol, strategyName, path = "./dump/partial") => {
12546
+ this.dump = async (symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) => {
11965
12547
  this.loggerService.log("partialMarkdownService dump", {
11966
12548
  symbol,
11967
12549
  strategyName,
11968
12550
  path,
11969
12551
  });
11970
12552
  const storage = this.getStorage(symbol, strategyName);
11971
- await storage.dump(symbol, strategyName, path);
12553
+ await storage.dump(symbol, strategyName, path, columns);
11972
12554
  };
11973
12555
  /**
11974
12556
  * Clears accumulated event data from storage.
@@ -12428,92 +13010,6 @@ class ConfigValidationService {
12428
13010
  }
12429
13011
  }
12430
13012
 
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
13013
  /** Maximum number of events to store in risk reports */
12518
13014
  const MAX_EVENTS = 250;
12519
13015
  /**
@@ -12569,9 +13065,10 @@ class ReportStorage {
12569
13065
  *
12570
13066
  * @param symbol - Trading pair symbol
12571
13067
  * @param strategyName - Strategy name
13068
+ * @param columns - Column configuration for formatting the table
12572
13069
  * @returns Markdown formatted report with all events
12573
13070
  */
12574
- async getReport(symbol, strategyName) {
13071
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) {
12575
13072
  const stats = await this.getData();
12576
13073
  if (stats.totalRejections === 0) {
12577
13074
  return [
@@ -12580,10 +13077,15 @@ class ReportStorage {
12580
13077
  "No risk rejections recorded yet.",
12581
13078
  ].join("\n");
12582
13079
  }
12583
- const visibleColumns = columns.filter((col) => col.isVisible());
13080
+ const visibleColumns = [];
13081
+ for (const col of columns) {
13082
+ if (await col.isVisible()) {
13083
+ visibleColumns.push(col);
13084
+ }
13085
+ }
12584
13086
  const header = visibleColumns.map((col) => col.label);
12585
13087
  const separator = visibleColumns.map(() => "---");
12586
- const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
13088
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
12587
13089
  const tableData = [header, separator, ...rows];
12588
13090
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
12589
13091
  return [
@@ -12606,9 +13108,10 @@ class ReportStorage {
12606
13108
  * @param symbol - Trading pair symbol
12607
13109
  * @param strategyName - Strategy name
12608
13110
  * @param path - Directory path to save report (default: "./dump/risk")
13111
+ * @param columns - Column configuration for formatting the table
12609
13112
  */
12610
- async dump(symbol, strategyName, path$1 = "./dump/risk") {
12611
- const markdown = await this.getReport(symbol, strategyName);
13113
+ async dump(symbol, strategyName, path$1 = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
13114
+ const markdown = await this.getReport(symbol, strategyName, columns);
12612
13115
  try {
12613
13116
  const dir = path.join(process.cwd(), path$1);
12614
13117
  await fs.mkdir(dir, { recursive: true });
@@ -12700,6 +13203,7 @@ class RiskMarkdownService {
12700
13203
  *
12701
13204
  * @param symbol - Trading pair symbol to generate report for
12702
13205
  * @param strategyName - Strategy name to generate report for
13206
+ * @param columns - Column configuration for formatting the table
12703
13207
  * @returns Markdown formatted report string with table of all events
12704
13208
  *
12705
13209
  * @example
@@ -12709,13 +13213,13 @@ class RiskMarkdownService {
12709
13213
  * console.log(markdown);
12710
13214
  * ```
12711
13215
  */
12712
- this.getReport = async (symbol, strategyName) => {
13216
+ this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) => {
12713
13217
  this.loggerService.log("riskMarkdownService getReport", {
12714
13218
  symbol,
12715
13219
  strategyName,
12716
13220
  });
12717
13221
  const storage = this.getStorage(symbol, strategyName);
12718
- return storage.getReport(symbol, strategyName);
13222
+ return storage.getReport(symbol, strategyName, columns);
12719
13223
  };
12720
13224
  /**
12721
13225
  * Saves symbol-strategy report to disk.
@@ -12725,6 +13229,7 @@ class RiskMarkdownService {
12725
13229
  * @param symbol - Trading pair symbol to save report for
12726
13230
  * @param strategyName - Strategy name to save report for
12727
13231
  * @param path - Directory path to save report (default: "./dump/risk")
13232
+ * @param columns - Column configuration for formatting the table
12728
13233
  *
12729
13234
  * @example
12730
13235
  * ```typescript
@@ -12737,14 +13242,14 @@ class RiskMarkdownService {
12737
13242
  * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
12738
13243
  * ```
12739
13244
  */
12740
- this.dump = async (symbol, strategyName, path = "./dump/risk") => {
13245
+ this.dump = async (symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) => {
12741
13246
  this.loggerService.log("riskMarkdownService dump", {
12742
13247
  symbol,
12743
13248
  strategyName,
12744
13249
  path,
12745
13250
  });
12746
13251
  const storage = this.getStorage(symbol, strategyName);
12747
- await storage.dump(symbol, strategyName, path);
13252
+ await storage.dump(symbol, strategyName, path, columns);
12748
13253
  };
12749
13254
  /**
12750
13255
  * Clears accumulated event data from storage.
@@ -12794,6 +13299,118 @@ class RiskMarkdownService {
12794
13299
  }
12795
13300
  }
12796
13301
 
13302
+ /**
13303
+ * Service for validating column configurations to ensure consistency with ColumnModel interface
13304
+ * and prevent invalid column definitions.
13305
+ *
13306
+ * Performs comprehensive validation on all column definitions in COLUMN_CONFIG:
13307
+ * - **Required fields**: All columns must have key, label, format, and isVisible properties
13308
+ * - **Unique keys**: All key values must be unique within each column collection
13309
+ * - **Function validation**: format and isVisible must be callable functions
13310
+ * - **Data types**: key and label must be non-empty strings
13311
+ *
13312
+ * @throws {Error} If any validation fails, throws with detailed breakdown of all errors
13313
+ *
13314
+ * @example
13315
+ * ```typescript
13316
+ * const validator = new ColumnValidationService();
13317
+ * validator.validate(); // Throws if column configuration is invalid
13318
+ * ```
13319
+ *
13320
+ * @example Validation failure output:
13321
+ * ```
13322
+ * Column configuration validation failed:
13323
+ * 1. backtest_columns[0]: Missing required field "format"
13324
+ * 2. heat_columns: Duplicate key "symbol" at indexes 1, 5
13325
+ * 3. live_columns[3].isVisible must be a function, got "boolean"
13326
+ * ```
13327
+ */
13328
+ class ColumnValidationService {
13329
+ constructor() {
13330
+ /**
13331
+ * @private
13332
+ * @readonly
13333
+ * Injected logger service instance
13334
+ */
13335
+ this.loggerService = inject(TYPES.loggerService);
13336
+ /**
13337
+ * Validates all column configurations in COLUMN_CONFIG for structural correctness.
13338
+ *
13339
+ * Checks:
13340
+ * 1. All required fields (key, label, format, isVisible) are present in each column
13341
+ * 2. key and label are non-empty strings
13342
+ * 3. format and isVisible are functions (not other types)
13343
+ * 4. All keys are unique within each column collection
13344
+ *
13345
+ * @throws Error if configuration is invalid
13346
+ */
13347
+ this.validate = () => {
13348
+ this.loggerService.log("columnValidationService validate");
13349
+ const errors = [];
13350
+ // Iterate through all column collections in COLUMN_CONFIG
13351
+ for (const [configKey, columns] of Object.entries(COLUMN_CONFIG)) {
13352
+ if (!Array.isArray(columns)) {
13353
+ errors.push(`${configKey} is not an array, got ${typeof columns}`);
13354
+ continue;
13355
+ }
13356
+ // Track keys for uniqueness check
13357
+ const keyMap = new Map();
13358
+ // Validate each column in the collection
13359
+ columns.forEach((column, index) => {
13360
+ if (!column || typeof column !== "object") {
13361
+ errors.push(`${configKey}[${index}]: Column must be an object, got ${typeof column}`);
13362
+ return;
13363
+ }
13364
+ // Check for all required fields
13365
+ const requiredFields = ["key", "label", "format", "isVisible"];
13366
+ for (const field of requiredFields) {
13367
+ if (!(field in column)) {
13368
+ errors.push(`${configKey}[${index}]: Missing required field "${field}"`);
13369
+ }
13370
+ }
13371
+ // Validate key and label are non-empty strings
13372
+ if (typeof column.key !== "string" || column.key.trim() === "") {
13373
+ errors.push(`${configKey}[${index}].key must be a non-empty string, got ${typeof column.key === "string" ? `"${column.key}"` : typeof column.key}`);
13374
+ }
13375
+ else {
13376
+ // Track key for uniqueness check
13377
+ if (!keyMap.has(column.key)) {
13378
+ keyMap.set(column.key, []);
13379
+ }
13380
+ keyMap.get(column.key).push(index);
13381
+ }
13382
+ if (typeof column.label !== "string" || column.label.trim() === "") {
13383
+ errors.push(`${configKey}[${index}].label must be a non-empty string, got ${typeof column.label === "string" ? `"${column.label}"` : typeof column.label}`);
13384
+ }
13385
+ // Validate format is a function
13386
+ if (typeof column.format !== "function") {
13387
+ errors.push(`${configKey}[${index}].format must be a function, got "${typeof column.format}"`);
13388
+ }
13389
+ // Validate isVisible is a function
13390
+ if (typeof column.isVisible !== "function") {
13391
+ errors.push(`${configKey}[${index}].isVisible must be a function, got "${typeof column.isVisible}"`);
13392
+ }
13393
+ });
13394
+ // Check for duplicate keys
13395
+ for (const [key, indexes] of keyMap.entries()) {
13396
+ if (indexes.length > 1) {
13397
+ errors.push(`${configKey}: Duplicate key "${key}" at indexes ${indexes.join(", ")}`);
13398
+ }
13399
+ }
13400
+ }
13401
+ // Throw aggregated errors if any
13402
+ if (errors.length > 0) {
13403
+ const errorMessage = `Column configuration validation failed:\n${errors
13404
+ .map((e, i) => ` ${i + 1}. ${e}`)
13405
+ .join("\n")}`;
13406
+ this.loggerService.warn(errorMessage);
13407
+ throw new Error(errorMessage);
13408
+ }
13409
+ this.loggerService.log("columnValidationService validation passed");
13410
+ };
13411
+ }
13412
+ }
13413
+
12797
13414
  {
12798
13415
  provide(TYPES.loggerService, () => new LoggerService());
12799
13416
  }
@@ -12865,6 +13482,7 @@ class RiskMarkdownService {
12865
13482
  provide(TYPES.riskValidationService, () => new RiskValidationService());
12866
13483
  provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
12867
13484
  provide(TYPES.configValidationService, () => new ConfigValidationService());
13485
+ provide(TYPES.columnValidationService, () => new ColumnValidationService());
12868
13486
  }
12869
13487
  {
12870
13488
  provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
@@ -12941,6 +13559,7 @@ const validationServices = {
12941
13559
  riskValidationService: inject(TYPES.riskValidationService),
12942
13560
  optimizerValidationService: inject(TYPES.optimizerValidationService),
12943
13561
  configValidationService: inject(TYPES.configValidationService),
13562
+ columnValidationService: inject(TYPES.columnValidationService),
12944
13563
  };
12945
13564
  const templateServices = {
12946
13565
  optimizerTemplateService: inject(TYPES.optimizerTemplateService),
@@ -13040,6 +13659,77 @@ function getConfig() {
13040
13659
  function getDefaultConfig() {
13041
13660
  return DEFAULT_CONFIG;
13042
13661
  }
13662
+ /**
13663
+ * Sets custom column configurations for markdown report generation.
13664
+ *
13665
+ * Allows overriding default column definitions for any report type.
13666
+ * All columns are validated before assignment to ensure structural correctness.
13667
+ *
13668
+ * @param columns - Partial column configuration object to override default column settings
13669
+ * @param _unsafe - Skip column validations - required for testbed
13670
+ *
13671
+ * @example
13672
+ * ```typescript
13673
+ * setColumns({
13674
+ * backtest_columns: [
13675
+ * {
13676
+ * key: "customId",
13677
+ * label: "Custom ID",
13678
+ * format: (data) => data.signal.id,
13679
+ * isVisible: () => true
13680
+ * }
13681
+ * ],
13682
+ * });
13683
+ * ```
13684
+ *
13685
+ * @throws {Error} If column configuration is invalid
13686
+ */
13687
+ function setColumns(columns, _unsafe) {
13688
+ const prevConfig = Object.assign({}, COLUMN_CONFIG);
13689
+ try {
13690
+ Object.assign(COLUMN_CONFIG, columns);
13691
+ !_unsafe && backtest$1.columnValidationService.validate();
13692
+ }
13693
+ catch (error) {
13694
+ console.warn(`backtest-kit setColumns failed: ${functoolsKit.getErrorMessage(error)}`, columns);
13695
+ Object.assign(COLUMN_CONFIG, prevConfig);
13696
+ throw error;
13697
+ }
13698
+ }
13699
+ /**
13700
+ * Retrieves a copy of the current column configuration for markdown report generation.
13701
+ *
13702
+ * Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
13703
+ * Use this to inspect the current column definitions without modifying them.
13704
+ *
13705
+ * @returns {ColumnConfig} A copy of the current column configuration object
13706
+ *
13707
+ * @example
13708
+ * ```typescript
13709
+ * const currentColumns = getColumns();
13710
+ * console.log(currentColumns.backtest_columns.length);
13711
+ * ```
13712
+ */
13713
+ function getColumns() {
13714
+ return Object.assign({}, COLUMN_CONFIG);
13715
+ }
13716
+ /**
13717
+ * Retrieves the default column configuration object for markdown report generation.
13718
+ *
13719
+ * Returns a reference to the default column definitions with all preset values.
13720
+ * Use this to see what column options are available and their default definitions.
13721
+ *
13722
+ * @returns {ColumnConfig} The default column configuration object
13723
+ *
13724
+ * @example
13725
+ * ```typescript
13726
+ * const defaultColumns = getDefaultColumns();
13727
+ * console.log(defaultColumns.backtest_columns);
13728
+ * ```
13729
+ */
13730
+ function getDefaultColumns() {
13731
+ return DEFAULT_COLUMNS;
13732
+ }
13043
13733
 
13044
13734
  const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
13045
13735
  const ADD_EXCHANGE_METHOD_NAME = "add.addExchange";
@@ -14919,12 +15609,12 @@ class BacktestInstance {
14919
15609
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
14920
15610
  }
14921
15611
  {
14922
- backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
15612
+ backtest$1.strategyCoreService.clear(true, { symbol, strategyName: context.strategyName });
14923
15613
  }
14924
15614
  {
14925
15615
  const { riskName, riskList } = backtest$1.strategySchemaService.get(context.strategyName);
14926
- riskName && backtest$1.riskGlobalService.clear(riskName);
14927
- riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(riskName));
15616
+ riskName && backtest$1.riskGlobalService.clear(true, riskName);
15617
+ riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(true, riskName));
14928
15618
  }
14929
15619
  return backtest$1.backtestCommandService.run(symbol, context);
14930
15620
  };
@@ -14955,9 +15645,9 @@ class BacktestInstance {
14955
15645
  });
14956
15646
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
14957
15647
  return () => {
14958
- backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, true);
15648
+ backtest$1.strategyCoreService.stop(true, { symbol, strategyName: context.strategyName });
14959
15649
  backtest$1.strategyCoreService
14960
- .getPendingSignal(symbol, context.strategyName)
15650
+ .getPendingSignal(true, symbol, context.strategyName)
14961
15651
  .then(async (pendingSignal) => {
14962
15652
  if (pendingSignal) {
14963
15653
  return;
@@ -14997,7 +15687,7 @@ class BacktestInstance {
14997
15687
  symbol,
14998
15688
  strategyName,
14999
15689
  });
15000
- await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
15690
+ await backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
15001
15691
  };
15002
15692
  /**
15003
15693
  * Gets statistical data from all closed signals for a symbol-strategy pair.
@@ -15025,6 +15715,7 @@ class BacktestInstance {
15025
15715
  *
15026
15716
  * @param symbol - Trading pair symbol
15027
15717
  * @param strategyName - Strategy name to generate report for
15718
+ * @param columns - Optional columns configuration for the report
15028
15719
  * @returns Promise resolving to markdown formatted report string
15029
15720
  *
15030
15721
  * @example
@@ -15034,12 +15725,12 @@ class BacktestInstance {
15034
15725
  * console.log(markdown);
15035
15726
  * ```
15036
15727
  */
15037
- this.getReport = async (symbol, strategyName) => {
15728
+ this.getReport = async (symbol, strategyName, columns) => {
15038
15729
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_REPORT, {
15039
15730
  symbol,
15040
15731
  strategyName,
15041
15732
  });
15042
- return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName);
15733
+ return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName, columns);
15043
15734
  };
15044
15735
  /**
15045
15736
  * Saves strategy report to disk.
@@ -15047,6 +15738,7 @@ class BacktestInstance {
15047
15738
  * @param symbol - Trading pair symbol
15048
15739
  * @param strategyName - Strategy name to save report for
15049
15740
  * @param path - Optional directory path to save report (default: "./dump/backtest")
15741
+ * @param columns - Optional columns configuration for the report
15050
15742
  *
15051
15743
  * @example
15052
15744
  * ```typescript
@@ -15058,13 +15750,13 @@ class BacktestInstance {
15058
15750
  * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
15059
15751
  * ```
15060
15752
  */
15061
- this.dump = async (symbol, strategyName, path) => {
15753
+ this.dump = async (symbol, strategyName, path, columns) => {
15062
15754
  backtest$1.loggerService.info(BACKTEST_METHOD_NAME_DUMP, {
15063
15755
  symbol,
15064
15756
  strategyName,
15065
15757
  path,
15066
15758
  });
15067
- await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path);
15759
+ await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path, columns);
15068
15760
  };
15069
15761
  }
15070
15762
  }
@@ -15203,6 +15895,7 @@ class BacktestUtils {
15203
15895
  *
15204
15896
  * @param symbol - Trading pair symbol
15205
15897
  * @param strategyName - Strategy name to generate report for
15898
+ * @param columns - Optional columns configuration for the report
15206
15899
  * @returns Promise resolving to markdown formatted report string
15207
15900
  *
15208
15901
  * @example
@@ -15211,7 +15904,7 @@ class BacktestUtils {
15211
15904
  * console.log(markdown);
15212
15905
  * ```
15213
15906
  */
15214
- this.getReport = async (symbol, strategyName) => {
15907
+ this.getReport = async (symbol, strategyName, columns) => {
15215
15908
  backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
15216
15909
  {
15217
15910
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15219,7 +15912,7 @@ class BacktestUtils {
15219
15912
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_REPORT));
15220
15913
  }
15221
15914
  const instance = this._getInstance(symbol, strategyName);
15222
- return await instance.getReport(symbol, strategyName);
15915
+ return await instance.getReport(symbol, strategyName, columns);
15223
15916
  };
15224
15917
  /**
15225
15918
  * Saves strategy report to disk.
@@ -15227,6 +15920,7 @@ class BacktestUtils {
15227
15920
  * @param symbol - Trading pair symbol
15228
15921
  * @param strategyName - Strategy name to save report for
15229
15922
  * @param path - Optional directory path to save report (default: "./dump/backtest")
15923
+ * @param columns - Optional columns configuration for the report
15230
15924
  *
15231
15925
  * @example
15232
15926
  * ```typescript
@@ -15237,7 +15931,7 @@ class BacktestUtils {
15237
15931
  * await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
15238
15932
  * ```
15239
15933
  */
15240
- this.dump = async (symbol, strategyName, path) => {
15934
+ this.dump = async (symbol, strategyName, path, columns) => {
15241
15935
  backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_DUMP);
15242
15936
  {
15243
15937
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15245,7 +15939,7 @@ class BacktestUtils {
15245
15939
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_DUMP));
15246
15940
  }
15247
15941
  const instance = this._getInstance(symbol, strategyName);
15248
- return await instance.dump(symbol, strategyName, path);
15942
+ return await instance.dump(symbol, strategyName, path, columns);
15249
15943
  };
15250
15944
  /**
15251
15945
  * Lists all active backtest instances with their current status.
@@ -15419,12 +16113,12 @@ class LiveInstance {
15419
16113
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
15420
16114
  }
15421
16115
  {
15422
- backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
16116
+ backtest$1.strategyCoreService.clear(false, { symbol, strategyName: context.strategyName });
15423
16117
  }
15424
16118
  {
15425
16119
  const { riskName, riskList } = backtest$1.strategySchemaService.get(context.strategyName);
15426
- riskName && backtest$1.riskGlobalService.clear(riskName);
15427
- riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(riskName));
16120
+ riskName && backtest$1.riskGlobalService.clear(false, riskName);
16121
+ riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(false, riskName));
15428
16122
  }
15429
16123
  return backtest$1.liveCommandService.run(symbol, context);
15430
16124
  };
@@ -15455,9 +16149,9 @@ class LiveInstance {
15455
16149
  });
15456
16150
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
15457
16151
  return () => {
15458
- backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }, false);
16152
+ backtest$1.strategyCoreService.stop(false, { symbol, strategyName: context.strategyName });
15459
16153
  backtest$1.strategyCoreService
15460
- .getPendingSignal(symbol, context.strategyName)
16154
+ .getPendingSignal(false, symbol, context.strategyName)
15461
16155
  .then(async (pendingSignal) => {
15462
16156
  if (pendingSignal) {
15463
16157
  return;
@@ -15497,7 +16191,7 @@ class LiveInstance {
15497
16191
  symbol,
15498
16192
  strategyName,
15499
16193
  });
15500
- await backtest$1.strategyCoreService.stop({ symbol, strategyName }, false);
16194
+ await backtest$1.strategyCoreService.stop(false, { symbol, strategyName });
15501
16195
  };
15502
16196
  /**
15503
16197
  * Gets statistical data from all live trading events for a symbol-strategy pair.
@@ -15525,6 +16219,7 @@ class LiveInstance {
15525
16219
  *
15526
16220
  * @param symbol - Trading pair symbol
15527
16221
  * @param strategyName - Strategy name to generate report for
16222
+ * @param columns - Optional columns configuration for the report
15528
16223
  * @returns Promise resolving to markdown formatted report string
15529
16224
  *
15530
16225
  * @example
@@ -15534,12 +16229,12 @@ class LiveInstance {
15534
16229
  * console.log(markdown);
15535
16230
  * ```
15536
16231
  */
15537
- this.getReport = async (symbol, strategyName) => {
16232
+ this.getReport = async (symbol, strategyName, columns) => {
15538
16233
  backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
15539
16234
  symbol,
15540
16235
  strategyName,
15541
16236
  });
15542
- return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
16237
+ return await backtest$1.liveMarkdownService.getReport(symbol, strategyName, columns);
15543
16238
  };
15544
16239
  /**
15545
16240
  * Saves strategy report to disk.
@@ -15547,6 +16242,7 @@ class LiveInstance {
15547
16242
  * @param symbol - Trading pair symbol
15548
16243
  * @param strategyName - Strategy name to save report for
15549
16244
  * @param path - Optional directory path to save report (default: "./dump/live")
16245
+ * @param columns - Optional columns configuration for the report
15550
16246
  *
15551
16247
  * @example
15552
16248
  * ```typescript
@@ -15558,13 +16254,13 @@ class LiveInstance {
15558
16254
  * await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
15559
16255
  * ```
15560
16256
  */
15561
- this.dump = async (symbol, strategyName, path) => {
16257
+ this.dump = async (symbol, strategyName, path, columns) => {
15562
16258
  backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
15563
16259
  symbol,
15564
16260
  strategyName,
15565
16261
  path,
15566
16262
  });
15567
- await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
16263
+ await backtest$1.liveMarkdownService.dump(symbol, strategyName, path, columns);
15568
16264
  };
15569
16265
  }
15570
16266
  }
@@ -15714,6 +16410,7 @@ class LiveUtils {
15714
16410
  *
15715
16411
  * @param symbol - Trading pair symbol
15716
16412
  * @param strategyName - Strategy name to generate report for
16413
+ * @param columns - Optional columns configuration for the report
15717
16414
  * @returns Promise resolving to markdown formatted report string
15718
16415
  *
15719
16416
  * @example
@@ -15722,7 +16419,7 @@ class LiveUtils {
15722
16419
  * console.log(markdown);
15723
16420
  * ```
15724
16421
  */
15725
- this.getReport = async (symbol, strategyName) => {
16422
+ this.getReport = async (symbol, strategyName, columns) => {
15726
16423
  backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_GET_REPORT);
15727
16424
  {
15728
16425
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15730,7 +16427,7 @@ class LiveUtils {
15730
16427
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
15731
16428
  }
15732
16429
  const instance = this._getInstance(symbol, strategyName);
15733
- return await instance.getReport(symbol, strategyName);
16430
+ return await instance.getReport(symbol, strategyName, columns);
15734
16431
  };
15735
16432
  /**
15736
16433
  * Saves strategy report to disk.
@@ -15738,6 +16435,7 @@ class LiveUtils {
15738
16435
  * @param symbol - Trading pair symbol
15739
16436
  * @param strategyName - Strategy name to save report for
15740
16437
  * @param path - Optional directory path to save report (default: "./dump/live")
16438
+ * @param columns - Optional columns configuration for the report
15741
16439
  *
15742
16440
  * @example
15743
16441
  * ```typescript
@@ -15748,7 +16446,7 @@ class LiveUtils {
15748
16446
  * await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
15749
16447
  * ```
15750
16448
  */
15751
- this.dump = async (symbol, strategyName, path) => {
16449
+ this.dump = async (symbol, strategyName, path, columns) => {
15752
16450
  backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_DUMP);
15753
16451
  {
15754
16452
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
@@ -15756,7 +16454,7 @@ class LiveUtils {
15756
16454
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
15757
16455
  }
15758
16456
  const instance = this._getInstance(symbol, strategyName);
15759
- return await instance.dump(symbol, strategyName, path);
16457
+ return await instance.dump(symbol, strategyName, path, columns);
15760
16458
  };
15761
16459
  /**
15762
16460
  * Lists all active live trading instances with their current status.
@@ -15855,6 +16553,7 @@ class ScheduleUtils {
15855
16553
  *
15856
16554
  * @param symbol - Trading pair symbol
15857
16555
  * @param strategyName - Strategy name to generate report for
16556
+ * @param columns - Optional columns configuration for the report
15858
16557
  * @returns Promise resolving to markdown formatted report string
15859
16558
  *
15860
16559
  * @example
@@ -15863,7 +16562,7 @@ class ScheduleUtils {
15863
16562
  * console.log(markdown);
15864
16563
  * ```
15865
16564
  */
15866
- this.getReport = async (symbol, strategyName) => {
16565
+ this.getReport = async (symbol, strategyName, columns) => {
15867
16566
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_REPORT, {
15868
16567
  symbol,
15869
16568
  strategyName,
@@ -15874,7 +16573,7 @@ class ScheduleUtils {
15874
16573
  riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
15875
16574
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT));
15876
16575
  }
15877
- return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName);
16576
+ return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName, columns);
15878
16577
  };
15879
16578
  /**
15880
16579
  * Saves strategy report to disk.
@@ -15882,6 +16581,7 @@ class ScheduleUtils {
15882
16581
  * @param symbol - Trading pair symbol
15883
16582
  * @param strategyName - Strategy name to save report for
15884
16583
  * @param path - Optional directory path to save report (default: "./dump/schedule")
16584
+ * @param columns - Optional columns configuration for the report
15885
16585
  *
15886
16586
  * @example
15887
16587
  * ```typescript
@@ -15892,7 +16592,7 @@ class ScheduleUtils {
15892
16592
  * await Schedule.dump("BTCUSDT", "my-strategy", "./custom/path");
15893
16593
  * ```
15894
16594
  */
15895
- this.dump = async (symbol, strategyName, path) => {
16595
+ this.dump = async (symbol, strategyName, path, columns) => {
15896
16596
  backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_DUMP, {
15897
16597
  symbol,
15898
16598
  strategyName,
@@ -15904,7 +16604,7 @@ class ScheduleUtils {
15904
16604
  riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
15905
16605
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP));
15906
16606
  }
15907
- await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path);
16607
+ await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path, columns);
15908
16608
  };
15909
16609
  }
15910
16610
  }
@@ -16004,6 +16704,7 @@ class Performance {
16004
16704
  *
16005
16705
  * @param symbol - Trading pair symbol
16006
16706
  * @param strategyName - Strategy name to generate report for
16707
+ * @param columns - Optional columns configuration for the report
16007
16708
  * @returns Markdown formatted report string
16008
16709
  *
16009
16710
  * @example
@@ -16016,14 +16717,14 @@ class Performance {
16016
16717
  * await fs.writeFile("performance-report.md", markdown);
16017
16718
  * ```
16018
16719
  */
16019
- static async getReport(symbol, strategyName) {
16720
+ static async getReport(symbol, strategyName, columns) {
16020
16721
  backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
16021
16722
  {
16022
16723
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16023
16724
  riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
16024
16725
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT));
16025
16726
  }
16026
- return backtest$1.performanceMarkdownService.getReport(symbol, strategyName);
16727
+ return backtest$1.performanceMarkdownService.getReport(symbol, strategyName, columns);
16027
16728
  }
16028
16729
  /**
16029
16730
  * Saves performance report to disk.
@@ -16034,6 +16735,7 @@ class Performance {
16034
16735
  * @param symbol - Trading pair symbol
16035
16736
  * @param strategyName - Strategy name to save report for
16036
16737
  * @param path - Optional custom directory path
16738
+ * @param columns - Optional columns configuration for the report
16037
16739
  *
16038
16740
  * @example
16039
16741
  * ```typescript
@@ -16044,14 +16746,14 @@ class Performance {
16044
16746
  * await Performance.dump("BTCUSDT", "my-strategy", "./reports/perf");
16045
16747
  * ```
16046
16748
  */
16047
- static async dump(symbol, strategyName, path = "./dump/performance") {
16749
+ static async dump(symbol, strategyName, path = "./dump/performance", columns) {
16048
16750
  backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_DUMP);
16049
16751
  {
16050
16752
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16051
16753
  riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
16052
16754
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP));
16053
16755
  }
16054
- return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path);
16756
+ return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path, columns);
16055
16757
  }
16056
16758
  }
16057
16759
 
@@ -16193,12 +16895,13 @@ class WalkerInstance {
16193
16895
  backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
16194
16896
  }
16195
16897
  {
16196
- backtest$1.strategyCoreService.clear({ symbol, strategyName });
16898
+ backtest$1.strategyCoreService.clear(true, { symbol, strategyName });
16197
16899
  }
16198
16900
  {
16199
16901
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16200
- riskName && backtest$1.riskGlobalService.clear(riskName);
16201
- riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(riskName));
16902
+ riskName && backtest$1.riskGlobalService.clear(true, riskName);
16903
+ riskList &&
16904
+ riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(true, riskName));
16202
16905
  }
16203
16906
  }
16204
16907
  return backtest$1.walkerCommandService.run(symbol, {
@@ -16234,8 +16937,12 @@ class WalkerInstance {
16234
16937
  this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
16235
16938
  return () => {
16236
16939
  for (const strategyName of walkerSchema.strategies) {
16237
- backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
16238
- walkerStopSubject.next({ symbol, strategyName, walkerName: context.walkerName });
16940
+ backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
16941
+ walkerStopSubject.next({
16942
+ symbol,
16943
+ strategyName,
16944
+ walkerName: context.walkerName,
16945
+ });
16239
16946
  }
16240
16947
  if (!this._isDone) {
16241
16948
  doneWalkerSubject.next({
@@ -16280,7 +16987,7 @@ class WalkerInstance {
16280
16987
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
16281
16988
  for (const strategyName of walkerSchema.strategies) {
16282
16989
  await walkerStopSubject.next({ symbol, strategyName, walkerName });
16283
- await backtest$1.strategyCoreService.stop({ symbol, strategyName }, true);
16990
+ await backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
16284
16991
  }
16285
16992
  };
16286
16993
  /**
@@ -16313,6 +17020,8 @@ class WalkerInstance {
16313
17020
  *
16314
17021
  * @param symbol - Trading symbol
16315
17022
  * @param walkerName - Walker name to generate report for
17023
+ * @param strategyColumns - Optional strategy columns configuration
17024
+ * @param pnlColumns - Optional PNL columns configuration
16316
17025
  * @returns Promise resolving to markdown formatted report string
16317
17026
  *
16318
17027
  * @example
@@ -16322,7 +17031,7 @@ class WalkerInstance {
16322
17031
  * console.log(markdown);
16323
17032
  * ```
16324
17033
  */
16325
- this.getReport = async (symbol, walkerName) => {
17034
+ this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
16326
17035
  backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_REPORT, {
16327
17036
  symbol,
16328
17037
  walkerName,
@@ -16331,7 +17040,7 @@ class WalkerInstance {
16331
17040
  return await backtest$1.walkerMarkdownService.getReport(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
16332
17041
  exchangeName: walkerSchema.exchangeName,
16333
17042
  frameName: walkerSchema.frameName,
16334
- });
17043
+ }, strategyColumns, pnlColumns);
16335
17044
  };
16336
17045
  /**
16337
17046
  * Saves walker report to disk.
@@ -16339,6 +17048,8 @@ class WalkerInstance {
16339
17048
  * @param symbol - Trading symbol
16340
17049
  * @param walkerName - Walker name to save report for
16341
17050
  * @param path - Optional directory path to save report (default: "./dump/walker")
17051
+ * @param strategyColumns - Optional strategy columns configuration
17052
+ * @param pnlColumns - Optional PNL columns configuration
16342
17053
  *
16343
17054
  * @example
16344
17055
  * ```typescript
@@ -16350,7 +17061,7 @@ class WalkerInstance {
16350
17061
  * await instance.dump("BTCUSDT", "my-walker", "./custom/path");
16351
17062
  * ```
16352
17063
  */
16353
- this.dump = async (symbol, walkerName, path) => {
17064
+ this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
16354
17065
  backtest$1.loggerService.info(WALKER_METHOD_NAME_DUMP, {
16355
17066
  symbol,
16356
17067
  walkerName,
@@ -16360,7 +17071,7 @@ class WalkerInstance {
16360
17071
  await backtest$1.walkerMarkdownService.dump(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
16361
17072
  exchangeName: walkerSchema.exchangeName,
16362
17073
  frameName: walkerSchema.frameName,
16363
- }, path);
17074
+ }, path, strategyColumns, pnlColumns);
16364
17075
  };
16365
17076
  }
16366
17077
  }
@@ -16405,8 +17116,10 @@ class WalkerUtils {
16405
17116
  for (const strategyName of walkerSchema.strategies) {
16406
17117
  backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_RUN);
16407
17118
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16408
- riskName && backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN);
16409
- riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN));
17119
+ riskName &&
17120
+ backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN);
17121
+ riskList &&
17122
+ riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN));
16410
17123
  }
16411
17124
  const instance = this._getInstance(symbol, context.walkerName);
16412
17125
  return instance.run(symbol, context);
@@ -16438,8 +17151,10 @@ class WalkerUtils {
16438
17151
  for (const strategyName of walkerSchema.strategies) {
16439
17152
  backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_BACKGROUND);
16440
17153
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16441
- riskName && backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND);
16442
- riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND));
17154
+ riskName &&
17155
+ backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND);
17156
+ riskList &&
17157
+ riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND));
16443
17158
  }
16444
17159
  const instance = this._getInstance(symbol, context.walkerName);
16445
17160
  return instance.background(symbol, context);
@@ -16473,8 +17188,10 @@ class WalkerUtils {
16473
17188
  for (const strategyName of walkerSchema.strategies) {
16474
17189
  backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_STOP);
16475
17190
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16476
- riskName && backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP);
16477
- riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP));
17191
+ riskName &&
17192
+ backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP);
17193
+ riskList &&
17194
+ riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP));
16478
17195
  }
16479
17196
  const instance = this._getInstance(symbol, walkerName);
16480
17197
  return await instance.stop(symbol, walkerName);
@@ -16498,8 +17215,10 @@ class WalkerUtils {
16498
17215
  for (const strategyName of walkerSchema.strategies) {
16499
17216
  backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_GET_DATA);
16500
17217
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16501
- riskName && backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA);
16502
- riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA));
17218
+ riskName &&
17219
+ backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA);
17220
+ riskList &&
17221
+ riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA));
16503
17222
  }
16504
17223
  const instance = this._getInstance(symbol, walkerName);
16505
17224
  return await instance.getData(symbol, walkerName);
@@ -16509,6 +17228,8 @@ class WalkerUtils {
16509
17228
  *
16510
17229
  * @param symbol - Trading symbol
16511
17230
  * @param walkerName - Walker name to generate report for
17231
+ * @param strategyColumns - Optional strategy columns configuration
17232
+ * @param pnlColumns - Optional PNL columns configuration
16512
17233
  * @returns Promise resolving to markdown formatted report string
16513
17234
  *
16514
17235
  * @example
@@ -16517,17 +17238,19 @@ class WalkerUtils {
16517
17238
  * console.log(markdown);
16518
17239
  * ```
16519
17240
  */
16520
- this.getReport = async (symbol, walkerName) => {
17241
+ this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
16521
17242
  backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_GET_REPORT);
16522
17243
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
16523
17244
  for (const strategyName of walkerSchema.strategies) {
16524
17245
  backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_GET_REPORT);
16525
17246
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16526
- riskName && backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT);
16527
- riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT));
17247
+ riskName &&
17248
+ backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT);
17249
+ riskList &&
17250
+ riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT));
16528
17251
  }
16529
17252
  const instance = this._getInstance(symbol, walkerName);
16530
- return await instance.getReport(symbol, walkerName);
17253
+ return await instance.getReport(symbol, walkerName, strategyColumns, pnlColumns);
16531
17254
  };
16532
17255
  /**
16533
17256
  * Saves walker report to disk.
@@ -16535,6 +17258,8 @@ class WalkerUtils {
16535
17258
  * @param symbol - Trading symbol
16536
17259
  * @param walkerName - Walker name to save report for
16537
17260
  * @param path - Optional directory path to save report (default: "./dump/walker")
17261
+ * @param strategyColumns - Optional strategy columns configuration
17262
+ * @param pnlColumns - Optional PNL columns configuration
16538
17263
  *
16539
17264
  * @example
16540
17265
  * ```typescript
@@ -16545,17 +17270,19 @@ class WalkerUtils {
16545
17270
  * await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
16546
17271
  * ```
16547
17272
  */
16548
- this.dump = async (symbol, walkerName, path) => {
17273
+ this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
16549
17274
  backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_DUMP);
16550
17275
  const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
16551
17276
  for (const strategyName of walkerSchema.strategies) {
16552
17277
  backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_DUMP);
16553
17278
  const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
16554
- riskName && backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP);
16555
- riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP));
17279
+ riskName &&
17280
+ backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP);
17281
+ riskList &&
17282
+ riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP));
16556
17283
  }
16557
17284
  const instance = this._getInstance(symbol, walkerName);
16558
- return await instance.dump(symbol, walkerName, path);
17285
+ return await instance.dump(symbol, walkerName, path, strategyColumns, pnlColumns);
16559
17286
  };
16560
17287
  /**
16561
17288
  * Lists all active walker instances with their current status.
@@ -16661,6 +17388,7 @@ class HeatUtils {
16661
17388
  * Symbols are sorted by Total PNL descending.
16662
17389
  *
16663
17390
  * @param strategyName - Strategy name to generate heatmap report for
17391
+ * @param columns - Optional columns configuration for the report
16664
17392
  * @returns Promise resolving to markdown formatted report string
16665
17393
  *
16666
17394
  * @example
@@ -16679,7 +17407,7 @@ class HeatUtils {
16679
17407
  * // ...
16680
17408
  * ```
16681
17409
  */
16682
- this.getReport = async (strategyName) => {
17410
+ this.getReport = async (strategyName, columns) => {
16683
17411
  backtest$1.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName });
16684
17412
  backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_GET_REPORT);
16685
17413
  {
@@ -16687,7 +17415,7 @@ class HeatUtils {
16687
17415
  riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
16688
17416
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT));
16689
17417
  }
16690
- return await backtest$1.heatMarkdownService.getReport(strategyName);
17418
+ return await backtest$1.heatMarkdownService.getReport(strategyName, columns);
16691
17419
  };
16692
17420
  /**
16693
17421
  * Saves heatmap report to disk for a strategy.
@@ -16697,6 +17425,7 @@ class HeatUtils {
16697
17425
  *
16698
17426
  * @param strategyName - Strategy name to save heatmap report for
16699
17427
  * @param path - Optional directory path to save report (default: "./dump/heatmap")
17428
+ * @param columns - Optional columns configuration for the report
16700
17429
  *
16701
17430
  * @example
16702
17431
  * ```typescript
@@ -16707,7 +17436,7 @@ class HeatUtils {
16707
17436
  * await Heat.dump("my-strategy", "./reports");
16708
17437
  * ```
16709
17438
  */
16710
- this.dump = async (strategyName, path) => {
17439
+ this.dump = async (strategyName, path, columns) => {
16711
17440
  backtest$1.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName, path });
16712
17441
  backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_DUMP);
16713
17442
  {
@@ -16715,7 +17444,7 @@ class HeatUtils {
16715
17444
  riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
16716
17445
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP));
16717
17446
  }
16718
- await backtest$1.heatMarkdownService.dump(strategyName, path);
17447
+ await backtest$1.heatMarkdownService.dump(strategyName, path, columns);
16719
17448
  };
16720
17449
  }
16721
17450
  }
@@ -17015,7 +17744,7 @@ class PartialUtils {
17015
17744
  *
17016
17745
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17017
17746
  * @param strategyName - Strategy name (e.g., "my-strategy")
17018
- * @returns Promise resolving to PartialStatistics object with counts and event list
17747
+ * @returns Promise resolving to PartialStatisticsModel object with counts and event list
17019
17748
  *
17020
17749
  * @example
17021
17750
  * ```typescript
@@ -17059,6 +17788,7 @@ class PartialUtils {
17059
17788
  *
17060
17789
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17061
17790
  * @param strategyName - Strategy name (e.g., "my-strategy")
17791
+ * @param columns - Optional columns configuration for the report
17062
17792
  * @returns Promise resolving to markdown formatted report string
17063
17793
  *
17064
17794
  * @example
@@ -17079,7 +17809,7 @@ class PartialUtils {
17079
17809
  * // **Loss events:** 1
17080
17810
  * ```
17081
17811
  */
17082
- this.getReport = async (symbol, strategyName) => {
17812
+ this.getReport = async (symbol, strategyName, columns) => {
17083
17813
  backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName });
17084
17814
  backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
17085
17815
  {
@@ -17087,7 +17817,7 @@ class PartialUtils {
17087
17817
  riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
17088
17818
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT));
17089
17819
  }
17090
- return await backtest$1.partialMarkdownService.getReport(symbol, strategyName);
17820
+ return await backtest$1.partialMarkdownService.getReport(symbol, strategyName, columns);
17091
17821
  };
17092
17822
  /**
17093
17823
  * Generates and saves markdown report to file.
@@ -17104,6 +17834,7 @@ class PartialUtils {
17104
17834
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17105
17835
  * @param strategyName - Strategy name (e.g., "my-strategy")
17106
17836
  * @param path - Output directory path (default: "./dump/partial")
17837
+ * @param columns - Optional columns configuration for the report
17107
17838
  * @returns Promise that resolves when file is written
17108
17839
  *
17109
17840
  * @example
@@ -17120,7 +17851,7 @@ class PartialUtils {
17120
17851
  * }
17121
17852
  * ```
17122
17853
  */
17123
- this.dump = async (symbol, strategyName, path) => {
17854
+ this.dump = async (symbol, strategyName, path, columns) => {
17124
17855
  backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName, path });
17125
17856
  backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_DUMP);
17126
17857
  {
@@ -17128,7 +17859,7 @@ class PartialUtils {
17128
17859
  riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
17129
17860
  riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP));
17130
17861
  }
17131
- await backtest$1.partialMarkdownService.dump(symbol, strategyName, path);
17862
+ await backtest$1.partialMarkdownService.dump(symbol, strategyName, path, columns);
17132
17863
  };
17133
17864
  }
17134
17865
  }
@@ -17260,8 +17991,10 @@ exports.formatPrice = formatPrice;
17260
17991
  exports.formatQuantity = formatQuantity;
17261
17992
  exports.getAveragePrice = getAveragePrice;
17262
17993
  exports.getCandles = getCandles;
17994
+ exports.getColumns = getColumns;
17263
17995
  exports.getConfig = getConfig;
17264
17996
  exports.getDate = getDate;
17997
+ exports.getDefaultColumns = getDefaultColumns;
17265
17998
  exports.getDefaultConfig = getDefaultConfig;
17266
17999
  exports.getMode = getMode;
17267
18000
  exports.lib = backtest;
@@ -17300,5 +18033,6 @@ exports.listenWalker = listenWalker;
17300
18033
  exports.listenWalkerComplete = listenWalkerComplete;
17301
18034
  exports.listenWalkerOnce = listenWalkerOnce;
17302
18035
  exports.listenWalkerProgress = listenWalkerProgress;
18036
+ exports.setColumns = setColumns;
17303
18037
  exports.setConfig = setConfig;
17304
18038
  exports.setLogger = setLogger;