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