backtest-kit 1.5.16 → 1.5.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -109,6 +109,14 @@ const GLOBAL_CONFIG = {
109
109
  * Example: 3 candles = 12 points (use average), 5 candles = 20 points (use median)
110
110
  */
111
111
  CC_GET_CANDLES_MIN_CANDLES_FOR_MEDIAN: 5,
112
+ /**
113
+ * Controls visibility of signal notes in markdown report tables.
114
+ * When enabled, the "Note" column will be displayed in all markdown reports
115
+ * (backtest, live, schedule, risk, etc.)
116
+ *
117
+ * Default: false (notes are hidden to reduce table width and improve readability)
118
+ */
119
+ CC_REPORT_SHOW_SIGNAL_NOTE: false,
112
120
  };
113
121
  const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
114
122
 
@@ -200,6 +208,7 @@ const markdownServices$1 = {
200
208
  heatMarkdownService: Symbol('heatMarkdownService'),
201
209
  partialMarkdownService: Symbol('partialMarkdownService'),
202
210
  outlineMarkdownService: Symbol('outlineMarkdownService'),
211
+ riskMarkdownService: Symbol('riskMarkdownService'),
203
212
  };
204
213
  const validationServices$1 = {
205
214
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -440,11 +449,12 @@ const GET_CANDLES_FN = async (dto, since, self) => {
440
449
  }
441
450
  catch (err) {
442
451
  const message = `ClientExchange GET_CANDLES_FN: attempt ${i + 1} failed for symbol=${dto.symbol}, interval=${dto.interval}, since=${since.toISOString()}, limit=${dto.limit}}`;
443
- self.params.logger.warn(message, {
452
+ const payload = {
444
453
  error: errorData(err),
445
454
  message: getErrorMessage(err),
446
- });
447
- console.warn(message);
455
+ };
456
+ self.params.logger.warn(message, payload);
457
+ console.warn(message, payload);
448
458
  lastError = err;
449
459
  await sleep(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS);
450
460
  }
@@ -1723,6 +1733,12 @@ const partialProfitSubject = new Subject();
1723
1733
  * Emits when a signal reaches a loss level (10%, 20%, 30%, etc).
1724
1734
  */
1725
1735
  const partialLossSubject = new Subject();
1736
+ /**
1737
+ * Risk rejection emitter for risk management violations.
1738
+ * Emits ONLY when a signal is rejected due to risk validation failure.
1739
+ * Does not emit for allowed signals (prevents spam).
1740
+ */
1741
+ const riskSubject = new Subject();
1726
1742
 
1727
1743
  var emitters = /*#__PURE__*/Object.freeze({
1728
1744
  __proto__: null,
@@ -1737,6 +1753,7 @@ var emitters = /*#__PURE__*/Object.freeze({
1737
1753
  progressBacktestEmitter: progressBacktestEmitter,
1738
1754
  progressOptimizerEmitter: progressOptimizerEmitter,
1739
1755
  progressWalkerEmitter: progressWalkerEmitter,
1756
+ riskSubject: riskSubject,
1740
1757
  signalBacktestEmitter: signalBacktestEmitter,
1741
1758
  signalEmitter: signalEmitter,
1742
1759
  signalLiveEmitter: signalLiveEmitter,
@@ -1823,6 +1840,9 @@ const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
1823
1840
  if (signal.position === undefined || signal.position === null) {
1824
1841
  errors.push('position is required and must be "long" or "short"');
1825
1842
  }
1843
+ if (signal.position !== "long" && signal.position !== "short") {
1844
+ errors.push(`position must be "long" or "short", got "${signal.position}"`);
1845
+ }
1826
1846
  // ЗАЩИТА ОТ NaN/Infinity: currentPrice должна быть конечным числом
1827
1847
  if (!isFinite(currentPrice)) {
1828
1848
  errors.push(`currentPrice must be a finite number, got ${currentPrice} (${typeof currentPrice})`);
@@ -2109,10 +2129,13 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
2109
2129
  }, {
2110
2130
  defaultValue: null,
2111
2131
  fallback: (error) => {
2112
- backtest$1.loggerService.warn("ClientStrategy exception thrown", {
2132
+ const message = "ClientStrategy exception thrown";
2133
+ const payload = {
2113
2134
  error: errorData(error),
2114
2135
  message: getErrorMessage(error),
2115
- });
2136
+ };
2137
+ backtest$1.loggerService.warn(message, payload);
2138
+ console.warn(message, payload);
2116
2139
  errorEmitter.next(error);
2117
2140
  },
2118
2141
  });
@@ -2803,7 +2826,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2803
2826
  // Moving towards TP
2804
2827
  const tpDistance = signal.priceTakeProfit - signal.priceOpen;
2805
2828
  const progressPercent = (currentDistance / tpDistance) * 100;
2806
- await self.params.partial.profit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2829
+ await self.params.partial.profit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, new Date(currentCandleTimestamp));
2807
2830
  if (self.params.callbacks?.onPartialProfit) {
2808
2831
  self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2809
2832
  }
@@ -2812,7 +2835,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2812
2835
  // Moving towards SL
2813
2836
  const slDistance = signal.priceOpen - signal.priceStopLoss;
2814
2837
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
2815
- await self.params.partial.loss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2838
+ await self.params.partial.loss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, new Date(currentCandleTimestamp));
2816
2839
  if (self.params.callbacks?.onPartialLoss) {
2817
2840
  self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2818
2841
  }
@@ -2825,7 +2848,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2825
2848
  // Moving towards TP
2826
2849
  const tpDistance = signal.priceOpen - signal.priceTakeProfit;
2827
2850
  const progressPercent = (currentDistance / tpDistance) * 100;
2828
- await self.params.partial.profit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2851
+ await self.params.partial.profit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, new Date(currentCandleTimestamp));
2829
2852
  if (self.params.callbacks?.onPartialProfit) {
2830
2853
  self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2831
2854
  }
@@ -2834,7 +2857,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2834
2857
  // Moving towards SL
2835
2858
  const slDistance = signal.priceStopLoss - signal.priceOpen;
2836
2859
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
2837
- await self.params.partial.loss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2860
+ await self.params.partial.loss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, new Date(currentCandleTimestamp));
2838
2861
  if (self.params.callbacks?.onPartialLoss) {
2839
2862
  self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2840
2863
  }
@@ -3789,10 +3812,13 @@ const DO_VALIDATION_FN = trycatch(async (validation, params) => {
3789
3812
  }, {
3790
3813
  defaultValue: false,
3791
3814
  fallback: (error) => {
3792
- backtest$1.loggerService.warn("ClientRisk exception thrown", {
3815
+ const message = "ClientRisk exception thrown";
3816
+ const payload = {
3793
3817
  error: errorData(error),
3794
3818
  message: getErrorMessage(error),
3795
- });
3819
+ };
3820
+ backtest$1.loggerService.warn(message, payload);
3821
+ console.warn(message, payload);
3796
3822
  validationSubject.next(error);
3797
3823
  },
3798
3824
  });
@@ -3862,17 +3888,25 @@ class ClientRisk {
3862
3888
  };
3863
3889
  // Execute custom validations
3864
3890
  let isValid = true;
3891
+ let rejectionNote = "N/A";
3865
3892
  if (this.params.validations) {
3866
3893
  for (const validation of this.params.validations) {
3867
3894
  if (not(await DO_VALIDATION_FN(typeof validation === "function"
3868
3895
  ? validation
3869
3896
  : validation.validate, payload))) {
3870
3897
  isValid = false;
3898
+ // Capture note from validation if available
3899
+ if (typeof validation !== "function" && validation.note) {
3900
+ rejectionNote = validation.note;
3901
+ }
3871
3902
  break;
3872
3903
  }
3873
3904
  }
3874
3905
  }
3875
3906
  if (!isValid) {
3907
+ // Call params.onRejected for riskSubject emission
3908
+ await this.params.onRejected(params.symbol, params, riskMap.size, rejectionNote, Date.now());
3909
+ // Call schema callbacks.onRejected if defined
3876
3910
  if (this.params.callbacks?.onRejected) {
3877
3911
  this.params.callbacks.onRejected(params.symbol, params);
3878
3912
  }
@@ -3935,6 +3969,28 @@ class ClientRisk {
3935
3969
  }
3936
3970
  }
3937
3971
 
3972
+ /**
3973
+ * Callback function for emitting risk rejection events to riskSubject.
3974
+ *
3975
+ * Called by ClientRisk when a signal is rejected due to risk validation failure.
3976
+ * Emits RiskContract event to all subscribers.
3977
+ *
3978
+ * @param symbol - Trading pair symbol
3979
+ * @param params - Risk check arguments
3980
+ * @param activePositionCount - Number of active positions at rejection time
3981
+ * @param comment - Rejection reason from validation note or "N/A"
3982
+ * @param timestamp - Event timestamp in milliseconds
3983
+ */
3984
+ const COMMIT_REJECTION_FN = async (symbol, params, activePositionCount, comment, timestamp) => await riskSubject.next({
3985
+ symbol,
3986
+ pendingSignal: params.pendingSignal,
3987
+ strategyName: params.strategyName,
3988
+ exchangeName: params.exchangeName,
3989
+ currentPrice: params.currentPrice,
3990
+ activePositionCount,
3991
+ comment,
3992
+ timestamp,
3993
+ });
3938
3994
  /**
3939
3995
  * Connection service routing risk operations to correct ClientRisk instance.
3940
3996
  *
@@ -3985,6 +4041,7 @@ class RiskConnectionService {
3985
4041
  return new ClientRisk({
3986
4042
  ...schema,
3987
4043
  logger: this.loggerService,
4044
+ onRejected: COMMIT_REJECTION_FN,
3988
4045
  });
3989
4046
  });
3990
4047
  /**
@@ -3992,6 +4049,7 @@ class RiskConnectionService {
3992
4049
  *
3993
4050
  * Routes to appropriate ClientRisk instance based on provided context.
3994
4051
  * Validates portfolio drawdown, symbol exposure, position count, and daily loss limits.
4052
+ * ClientRisk will emit riskSubject event via onRejected callback when signal is rejected.
3995
4053
  *
3996
4054
  * @param params - Risk check arguments (portfolio state, position details)
3997
4055
  * @param context - Execution context with risk name
@@ -5980,46 +6038,54 @@ function isUnsafe$3(value) {
5980
6038
  }
5981
6039
  return false;
5982
6040
  }
5983
- const columns$4 = [
6041
+ const columns$6 = [
5984
6042
  {
5985
6043
  key: "signalId",
5986
6044
  label: "Signal ID",
5987
6045
  format: (data) => data.signal.id,
6046
+ isVisible: () => true,
5988
6047
  },
5989
6048
  {
5990
6049
  key: "symbol",
5991
6050
  label: "Symbol",
5992
6051
  format: (data) => data.signal.symbol,
6052
+ isVisible: () => true,
5993
6053
  },
5994
6054
  {
5995
6055
  key: "position",
5996
6056
  label: "Position",
5997
6057
  format: (data) => data.signal.position.toUpperCase(),
6058
+ isVisible: () => true,
5998
6059
  },
5999
6060
  {
6000
6061
  key: "note",
6001
6062
  label: "Note",
6002
6063
  format: (data) => toPlainString(data.signal.note ?? "N/A"),
6064
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
6003
6065
  },
6004
6066
  {
6005
6067
  key: "openPrice",
6006
6068
  label: "Open Price",
6007
6069
  format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
6070
+ isVisible: () => true,
6008
6071
  },
6009
6072
  {
6010
6073
  key: "closePrice",
6011
6074
  label: "Close Price",
6012
6075
  format: (data) => `${data.currentPrice.toFixed(8)} USD`,
6076
+ isVisible: () => true,
6013
6077
  },
6014
6078
  {
6015
6079
  key: "takeProfit",
6016
6080
  label: "Take Profit",
6017
6081
  format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
6082
+ isVisible: () => true,
6018
6083
  },
6019
6084
  {
6020
6085
  key: "stopLoss",
6021
6086
  label: "Stop Loss",
6022
6087
  format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
6088
+ isVisible: () => true,
6023
6089
  },
6024
6090
  {
6025
6091
  key: "pnl",
@@ -6028,11 +6094,13 @@ const columns$4 = [
6028
6094
  const pnlPercentage = data.pnl.pnlPercentage;
6029
6095
  return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
6030
6096
  },
6097
+ isVisible: () => true,
6031
6098
  },
6032
6099
  {
6033
6100
  key: "closeReason",
6034
6101
  label: "Close Reason",
6035
6102
  format: (data) => data.closeReason,
6103
+ isVisible: () => true,
6036
6104
  },
6037
6105
  {
6038
6106
  key: "duration",
@@ -6042,23 +6110,28 @@ const columns$4 = [
6042
6110
  const durationMin = Math.round(durationMs / 60000);
6043
6111
  return `${durationMin}`;
6044
6112
  },
6113
+ isVisible: () => true,
6045
6114
  },
6046
6115
  {
6047
6116
  key: "openTimestamp",
6048
6117
  label: "Open Time",
6049
6118
  format: (data) => new Date(data.signal.pendingAt).toISOString(),
6119
+ isVisible: () => true,
6050
6120
  },
6051
6121
  {
6052
6122
  key: "closeTimestamp",
6053
6123
  label: "Close Time",
6054
6124
  format: (data) => new Date(data.closeTimestamp).toISOString(),
6125
+ isVisible: () => true,
6055
6126
  },
6056
6127
  ];
6128
+ /** Maximum number of signals to store in backtest reports */
6129
+ const MAX_EVENTS$6 = 250;
6057
6130
  /**
6058
6131
  * Storage class for accumulating closed signals per strategy.
6059
6132
  * Maintains a list of all closed signals and provides methods to generate reports.
6060
6133
  */
6061
- let ReportStorage$4 = class ReportStorage {
6134
+ let ReportStorage$5 = class ReportStorage {
6062
6135
  constructor() {
6063
6136
  /** Internal list of all closed signals for this strategy */
6064
6137
  this._signalList = [];
@@ -6069,7 +6142,11 @@ let ReportStorage$4 = class ReportStorage {
6069
6142
  * @param data - Closed signal data with PNL and close reason
6070
6143
  */
6071
6144
  addSignal(data) {
6072
- this._signalList.push(data);
6145
+ this._signalList.unshift(data);
6146
+ // Trim queue if exceeded MAX_EVENTS
6147
+ if (this._signalList.length > MAX_EVENTS$6) {
6148
+ this._signalList.pop();
6149
+ }
6073
6150
  }
6074
6151
  /**
6075
6152
  * Calculates statistical data from closed signals (Controller).
@@ -6152,9 +6229,10 @@ let ReportStorage$4 = class ReportStorage {
6152
6229
  "No signals closed yet."
6153
6230
  ].join("\n");
6154
6231
  }
6155
- const header = columns$4.map((col) => col.label);
6156
- const separator = columns$4.map(() => "---");
6157
- const rows = this._signalList.map((closedSignal) => columns$4.map((col) => col.format(closedSignal)));
6232
+ const visibleColumns = columns$6.filter((col) => col.isVisible());
6233
+ const header = visibleColumns.map((col) => col.label);
6234
+ const separator = visibleColumns.map(() => "---");
6235
+ const rows = this._signalList.map((closedSignal) => visibleColumns.map((col) => col.format(closedSignal)));
6158
6236
  const tableData = [header, separator, ...rows];
6159
6237
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6160
6238
  return [
@@ -6230,7 +6308,7 @@ class BacktestMarkdownService {
6230
6308
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
6231
6309
  * Each symbol-strategy combination gets its own isolated storage instance.
6232
6310
  */
6233
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$4());
6311
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$5());
6234
6312
  /**
6235
6313
  * Processes tick events and accumulates closed signals.
6236
6314
  * Should be called from IStrategyCallbacks.onTick.
@@ -6401,46 +6479,54 @@ function isUnsafe$2(value) {
6401
6479
  }
6402
6480
  return false;
6403
6481
  }
6404
- const columns$3 = [
6482
+ const columns$5 = [
6405
6483
  {
6406
6484
  key: "timestamp",
6407
6485
  label: "Timestamp",
6408
6486
  format: (data) => new Date(data.timestamp).toISOString(),
6487
+ isVisible: () => true,
6409
6488
  },
6410
6489
  {
6411
6490
  key: "action",
6412
6491
  label: "Action",
6413
6492
  format: (data) => data.action.toUpperCase(),
6493
+ isVisible: () => true,
6414
6494
  },
6415
6495
  {
6416
6496
  key: "symbol",
6417
6497
  label: "Symbol",
6418
6498
  format: (data) => data.symbol ?? "N/A",
6499
+ isVisible: () => true,
6419
6500
  },
6420
6501
  {
6421
6502
  key: "signalId",
6422
6503
  label: "Signal ID",
6423
6504
  format: (data) => data.signalId ?? "N/A",
6505
+ isVisible: () => true,
6424
6506
  },
6425
6507
  {
6426
6508
  key: "position",
6427
6509
  label: "Position",
6428
6510
  format: (data) => data.position?.toUpperCase() ?? "N/A",
6511
+ isVisible: () => true,
6429
6512
  },
6430
6513
  {
6431
6514
  key: "note",
6432
6515
  label: "Note",
6433
6516
  format: (data) => toPlainString(data.note ?? "N/A"),
6517
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
6434
6518
  },
6435
6519
  {
6436
6520
  key: "currentPrice",
6437
6521
  label: "Current Price",
6438
6522
  format: (data) => `${data.currentPrice.toFixed(8)} USD`,
6523
+ isVisible: () => true,
6439
6524
  },
6440
6525
  {
6441
6526
  key: "openPrice",
6442
6527
  label: "Open Price",
6443
6528
  format: (data) => data.openPrice !== undefined ? `${data.openPrice.toFixed(8)} USD` : "N/A",
6529
+ isVisible: () => true,
6444
6530
  },
6445
6531
  {
6446
6532
  key: "takeProfit",
@@ -6448,21 +6534,25 @@ const columns$3 = [
6448
6534
  format: (data) => data.takeProfit !== undefined
6449
6535
  ? `${data.takeProfit.toFixed(8)} USD`
6450
6536
  : "N/A",
6537
+ isVisible: () => true,
6451
6538
  },
6452
6539
  {
6453
6540
  key: "stopLoss",
6454
6541
  label: "Stop Loss",
6455
6542
  format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
6543
+ isVisible: () => true,
6456
6544
  },
6457
6545
  {
6458
6546
  key: "percentTp",
6459
6547
  label: "% to TP",
6460
6548
  format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
6549
+ isVisible: () => true,
6461
6550
  },
6462
6551
  {
6463
6552
  key: "percentSl",
6464
6553
  label: "% to SL",
6465
6554
  format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
6555
+ isVisible: () => true,
6466
6556
  },
6467
6557
  {
6468
6558
  key: "pnl",
@@ -6472,25 +6562,28 @@ const columns$3 = [
6472
6562
  return "N/A";
6473
6563
  return `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`;
6474
6564
  },
6565
+ isVisible: () => true,
6475
6566
  },
6476
6567
  {
6477
6568
  key: "closeReason",
6478
6569
  label: "Close Reason",
6479
6570
  format: (data) => data.closeReason ?? "N/A",
6571
+ isVisible: () => true,
6480
6572
  },
6481
6573
  {
6482
6574
  key: "duration",
6483
6575
  label: "Duration (min)",
6484
6576
  format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
6577
+ isVisible: () => true,
6485
6578
  },
6486
6579
  ];
6487
6580
  /** Maximum number of events to store in live trading reports */
6488
- const MAX_EVENTS$4 = 250;
6581
+ const MAX_EVENTS$5 = 250;
6489
6582
  /**
6490
6583
  * Storage class for accumulating all tick events per strategy.
6491
6584
  * Maintains a chronological list of all events (idle, opened, active, closed).
6492
6585
  */
6493
- let ReportStorage$3 = class ReportStorage {
6586
+ let ReportStorage$4 = class ReportStorage {
6494
6587
  constructor() {
6495
6588
  /** Internal list of all tick events for this strategy */
6496
6589
  this._eventList = [];
@@ -6517,9 +6610,9 @@ let ReportStorage$3 = class ReportStorage {
6517
6610
  return;
6518
6611
  }
6519
6612
  {
6520
- this._eventList.push(newEvent);
6521
- if (this._eventList.length > MAX_EVENTS$4) {
6522
- this._eventList.shift();
6613
+ this._eventList.unshift(newEvent);
6614
+ if (this._eventList.length > MAX_EVENTS$5) {
6615
+ this._eventList.pop();
6523
6616
  }
6524
6617
  }
6525
6618
  }
@@ -6529,7 +6622,7 @@ let ReportStorage$3 = class ReportStorage {
6529
6622
  * @param data - Opened tick result
6530
6623
  */
6531
6624
  addOpenedEvent(data) {
6532
- this._eventList.push({
6625
+ this._eventList.unshift({
6533
6626
  timestamp: data.signal.pendingAt,
6534
6627
  action: "opened",
6535
6628
  symbol: data.signal.symbol,
@@ -6542,12 +6635,13 @@ let ReportStorage$3 = class ReportStorage {
6542
6635
  stopLoss: data.signal.priceStopLoss,
6543
6636
  });
6544
6637
  // Trim queue if exceeded MAX_EVENTS
6545
- if (this._eventList.length > MAX_EVENTS$4) {
6546
- this._eventList.shift();
6638
+ if (this._eventList.length > MAX_EVENTS$5) {
6639
+ this._eventList.pop();
6547
6640
  }
6548
6641
  }
6549
6642
  /**
6550
6643
  * Adds an active event to the storage.
6644
+ * Replaces the last active event with the same signalId.
6551
6645
  *
6552
6646
  * @param data - Active tick result
6553
6647
  */
@@ -6566,10 +6660,18 @@ let ReportStorage$3 = class ReportStorage {
6566
6660
  percentTp: data.percentTp,
6567
6661
  percentSl: data.percentSl,
6568
6662
  };
6569
- this._eventList.push(newEvent);
6663
+ // Find the last active event with the same signalId
6664
+ const lastActiveIndex = this._eventList.findLastIndex((event) => event.action === "active" && event.signalId === data.signal.id);
6665
+ // Replace the last active event with the same signalId
6666
+ if (lastActiveIndex !== -1) {
6667
+ this._eventList[lastActiveIndex] = newEvent;
6668
+ return;
6669
+ }
6670
+ // If no previous active event found, add new event
6671
+ this._eventList.unshift(newEvent);
6570
6672
  // Trim queue if exceeded MAX_EVENTS
6571
- if (this._eventList.length > MAX_EVENTS$4) {
6572
- this._eventList.shift();
6673
+ if (this._eventList.length > MAX_EVENTS$5) {
6674
+ this._eventList.pop();
6573
6675
  }
6574
6676
  }
6575
6677
  /**
@@ -6595,10 +6697,10 @@ let ReportStorage$3 = class ReportStorage {
6595
6697
  closeReason: data.closeReason,
6596
6698
  duration: durationMin,
6597
6699
  };
6598
- this._eventList.push(newEvent);
6700
+ this._eventList.unshift(newEvent);
6599
6701
  // Trim queue if exceeded MAX_EVENTS
6600
- if (this._eventList.length > MAX_EVENTS$4) {
6601
- this._eventList.shift();
6702
+ if (this._eventList.length > MAX_EVENTS$5) {
6703
+ this._eventList.pop();
6602
6704
  }
6603
6705
  }
6604
6706
  /**
@@ -6697,9 +6799,10 @@ let ReportStorage$3 = class ReportStorage {
6697
6799
  "No events recorded yet."
6698
6800
  ].join("\n");
6699
6801
  }
6700
- const header = columns$3.map((col) => col.label);
6701
- const separator = columns$3.map(() => "---");
6702
- const rows = this._eventList.map((event) => columns$3.map((col) => col.format(event)));
6802
+ const visibleColumns = columns$5.filter((col) => col.isVisible());
6803
+ const header = visibleColumns.map((col) => col.label);
6804
+ const separator = visibleColumns.map(() => "---");
6805
+ const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
6703
6806
  const tableData = [header, separator, ...rows];
6704
6807
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6705
6808
  return [
@@ -6778,7 +6881,7 @@ class LiveMarkdownService {
6778
6881
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
6779
6882
  * Each symbol-strategy combination gets its own isolated storage instance.
6780
6883
  */
6781
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$3());
6884
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$4());
6782
6885
  /**
6783
6886
  * Processes tick events and accumulates all event types.
6784
6887
  * Should be called from IStrategyCallbacks.onTick.
@@ -6941,70 +7044,81 @@ class LiveMarkdownService {
6941
7044
  }
6942
7045
  }
6943
7046
 
6944
- const columns$2 = [
7047
+ const columns$4 = [
6945
7048
  {
6946
7049
  key: "timestamp",
6947
7050
  label: "Timestamp",
6948
7051
  format: (data) => new Date(data.timestamp).toISOString(),
7052
+ isVisible: () => true,
6949
7053
  },
6950
7054
  {
6951
7055
  key: "action",
6952
7056
  label: "Action",
6953
7057
  format: (data) => data.action.toUpperCase(),
7058
+ isVisible: () => true,
6954
7059
  },
6955
7060
  {
6956
7061
  key: "symbol",
6957
7062
  label: "Symbol",
6958
7063
  format: (data) => data.symbol,
7064
+ isVisible: () => true,
6959
7065
  },
6960
7066
  {
6961
7067
  key: "signalId",
6962
7068
  label: "Signal ID",
6963
7069
  format: (data) => data.signalId,
7070
+ isVisible: () => true,
6964
7071
  },
6965
7072
  {
6966
7073
  key: "position",
6967
7074
  label: "Position",
6968
7075
  format: (data) => data.position.toUpperCase(),
7076
+ isVisible: () => true,
6969
7077
  },
6970
7078
  {
6971
7079
  key: "note",
6972
7080
  label: "Note",
6973
7081
  format: (data) => toPlainString(data.note ?? "N/A"),
7082
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
6974
7083
  },
6975
7084
  {
6976
7085
  key: "currentPrice",
6977
7086
  label: "Current Price",
6978
7087
  format: (data) => `${data.currentPrice.toFixed(8)} USD`,
7088
+ isVisible: () => true,
6979
7089
  },
6980
7090
  {
6981
7091
  key: "priceOpen",
6982
7092
  label: "Entry Price",
6983
7093
  format: (data) => `${data.priceOpen.toFixed(8)} USD`,
7094
+ isVisible: () => true,
6984
7095
  },
6985
7096
  {
6986
7097
  key: "takeProfit",
6987
7098
  label: "Take Profit",
6988
7099
  format: (data) => `${data.takeProfit.toFixed(8)} USD`,
7100
+ isVisible: () => true,
6989
7101
  },
6990
7102
  {
6991
7103
  key: "stopLoss",
6992
7104
  label: "Stop Loss",
6993
7105
  format: (data) => `${data.stopLoss.toFixed(8)} USD`,
7106
+ isVisible: () => true,
6994
7107
  },
6995
7108
  {
6996
7109
  key: "duration",
6997
7110
  label: "Wait Time (min)",
6998
7111
  format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
7112
+ isVisible: () => true,
6999
7113
  },
7000
7114
  ];
7001
7115
  /** Maximum number of events to store in schedule reports */
7002
- const MAX_EVENTS$3 = 250;
7116
+ const MAX_EVENTS$4 = 250;
7003
7117
  /**
7004
7118
  * Storage class for accumulating scheduled signal events per strategy.
7005
7119
  * Maintains a chronological list of scheduled and cancelled events.
7006
7120
  */
7007
- let ReportStorage$2 = class ReportStorage {
7121
+ let ReportStorage$3 = class ReportStorage {
7008
7122
  constructor() {
7009
7123
  /** Internal list of all scheduled events for this strategy */
7010
7124
  this._eventList = [];
@@ -7015,7 +7129,7 @@ let ReportStorage$2 = class ReportStorage {
7015
7129
  * @param data - Scheduled tick result
7016
7130
  */
7017
7131
  addScheduledEvent(data) {
7018
- this._eventList.push({
7132
+ this._eventList.unshift({
7019
7133
  timestamp: data.signal.scheduledAt,
7020
7134
  action: "scheduled",
7021
7135
  symbol: data.signal.symbol,
@@ -7028,8 +7142,8 @@ let ReportStorage$2 = class ReportStorage {
7028
7142
  stopLoss: data.signal.priceStopLoss,
7029
7143
  });
7030
7144
  // Trim queue if exceeded MAX_EVENTS
7031
- if (this._eventList.length > MAX_EVENTS$3) {
7032
- this._eventList.shift();
7145
+ if (this._eventList.length > MAX_EVENTS$4) {
7146
+ this._eventList.pop();
7033
7147
  }
7034
7148
  }
7035
7149
  /**
@@ -7053,10 +7167,10 @@ let ReportStorage$2 = class ReportStorage {
7053
7167
  stopLoss: data.signal.priceStopLoss,
7054
7168
  duration: durationMin,
7055
7169
  };
7056
- this._eventList.push(newEvent);
7170
+ this._eventList.unshift(newEvent);
7057
7171
  // Trim queue if exceeded MAX_EVENTS
7058
- if (this._eventList.length > MAX_EVENTS$3) {
7059
- this._eventList.shift();
7172
+ if (this._eventList.length > MAX_EVENTS$4) {
7173
+ this._eventList.pop();
7060
7174
  }
7061
7175
  }
7062
7176
  /**
@@ -7081,10 +7195,10 @@ let ReportStorage$2 = class ReportStorage {
7081
7195
  closeTimestamp: data.closeTimestamp,
7082
7196
  duration: durationMin,
7083
7197
  };
7084
- this._eventList.push(newEvent);
7198
+ this._eventList.unshift(newEvent);
7085
7199
  // Trim queue if exceeded MAX_EVENTS
7086
- if (this._eventList.length > MAX_EVENTS$3) {
7087
- this._eventList.shift();
7200
+ if (this._eventList.length > MAX_EVENTS$4) {
7201
+ this._eventList.pop();
7088
7202
  }
7089
7203
  }
7090
7204
  /**
@@ -7153,9 +7267,10 @@ let ReportStorage$2 = class ReportStorage {
7153
7267
  "No scheduled signals recorded yet."
7154
7268
  ].join("\n");
7155
7269
  }
7156
- const header = columns$2.map((col) => col.label);
7157
- const separator = columns$2.map(() => "---");
7158
- const rows = this._eventList.map((event) => columns$2.map((col) => col.format(event)));
7270
+ const visibleColumns = columns$4.filter((col) => col.isVisible());
7271
+ const header = visibleColumns.map((col) => col.label);
7272
+ const separator = visibleColumns.map(() => "---");
7273
+ const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
7159
7274
  const tableData = [header, separator, ...rows];
7160
7275
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7161
7276
  return [
@@ -7223,7 +7338,7 @@ class ScheduleMarkdownService {
7223
7338
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
7224
7339
  * Each symbol-strategy combination gets its own isolated storage instance.
7225
7340
  */
7226
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$2());
7341
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$3());
7227
7342
  /**
7228
7343
  * Processes tick events and accumulates scheduled/opened/cancelled events.
7229
7344
  * Should be called from signalEmitter subscription.
@@ -7389,8 +7504,88 @@ function percentile(sortedArray, p) {
7389
7504
  const index = Math.ceil((sortedArray.length * p) / 100) - 1;
7390
7505
  return sortedArray[Math.max(0, index)];
7391
7506
  }
7507
+ const columns$3 = [
7508
+ {
7509
+ key: "metricType",
7510
+ label: "Metric Type",
7511
+ format: (data) => data.metricType,
7512
+ isVisible: () => true,
7513
+ },
7514
+ {
7515
+ key: "count",
7516
+ label: "Count",
7517
+ format: (data) => data.count.toString(),
7518
+ isVisible: () => true,
7519
+ },
7520
+ {
7521
+ key: "totalDuration",
7522
+ label: "Total (ms)",
7523
+ format: (data) => data.totalDuration.toFixed(2),
7524
+ isVisible: () => true,
7525
+ },
7526
+ {
7527
+ key: "avgDuration",
7528
+ label: "Avg (ms)",
7529
+ format: (data) => data.avgDuration.toFixed(2),
7530
+ isVisible: () => true,
7531
+ },
7532
+ {
7533
+ key: "minDuration",
7534
+ label: "Min (ms)",
7535
+ format: (data) => data.minDuration.toFixed(2),
7536
+ isVisible: () => true,
7537
+ },
7538
+ {
7539
+ key: "maxDuration",
7540
+ label: "Max (ms)",
7541
+ format: (data) => data.maxDuration.toFixed(2),
7542
+ isVisible: () => true,
7543
+ },
7544
+ {
7545
+ key: "stdDev",
7546
+ label: "Std Dev (ms)",
7547
+ format: (data) => data.stdDev.toFixed(2),
7548
+ isVisible: () => true,
7549
+ },
7550
+ {
7551
+ key: "median",
7552
+ label: "Median (ms)",
7553
+ format: (data) => data.median.toFixed(2),
7554
+ isVisible: () => true,
7555
+ },
7556
+ {
7557
+ key: "p95",
7558
+ label: "P95 (ms)",
7559
+ format: (data) => data.p95.toFixed(2),
7560
+ isVisible: () => true,
7561
+ },
7562
+ {
7563
+ key: "p99",
7564
+ label: "P99 (ms)",
7565
+ format: (data) => data.p99.toFixed(2),
7566
+ isVisible: () => true,
7567
+ },
7568
+ {
7569
+ key: "avgWaitTime",
7570
+ label: "Avg Wait (ms)",
7571
+ format: (data) => data.avgWaitTime.toFixed(2),
7572
+ isVisible: () => true,
7573
+ },
7574
+ {
7575
+ key: "minWaitTime",
7576
+ label: "Min Wait (ms)",
7577
+ format: (data) => data.minWaitTime.toFixed(2),
7578
+ isVisible: () => true,
7579
+ },
7580
+ {
7581
+ key: "maxWaitTime",
7582
+ label: "Max Wait (ms)",
7583
+ format: (data) => data.maxWaitTime.toFixed(2),
7584
+ isVisible: () => true,
7585
+ },
7586
+ ];
7392
7587
  /** Maximum number of performance events to store per strategy */
7393
- const MAX_EVENTS$2 = 10000;
7588
+ const MAX_EVENTS$3 = 10000;
7394
7589
  /**
7395
7590
  * Storage class for accumulating performance metrics per strategy.
7396
7591
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -7406,10 +7601,10 @@ class PerformanceStorage {
7406
7601
  * @param event - Performance event with timing data
7407
7602
  */
7408
7603
  addEvent(event) {
7409
- this._events.push(event);
7604
+ this._events.unshift(event);
7410
7605
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
7411
- if (this._events.length > MAX_EVENTS$2) {
7412
- this._events.shift();
7606
+ if (this._events.length > MAX_EVENTS$3) {
7607
+ this._events.pop();
7413
7608
  }
7414
7609
  }
7415
7610
  /**
@@ -7504,40 +7699,13 @@ class PerformanceStorage {
7504
7699
  }
7505
7700
  // Sort metrics by total duration (descending) to show bottlenecks first
7506
7701
  const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
7507
- // Generate summary table
7508
- const summaryHeader = [
7509
- "Metric Type",
7510
- "Count",
7511
- "Total (ms)",
7512
- "Avg (ms)",
7513
- "Min (ms)",
7514
- "Max (ms)",
7515
- "Std Dev (ms)",
7516
- "Median (ms)",
7517
- "P95 (ms)",
7518
- "P99 (ms)",
7519
- "Avg Wait (ms)",
7520
- "Min Wait (ms)",
7521
- "Max Wait (ms)",
7522
- ];
7523
- const summarySeparator = summaryHeader.map(() => "---");
7524
- const summaryRows = sortedMetrics.map((metric) => [
7525
- metric.metricType,
7526
- metric.count.toString(),
7527
- metric.totalDuration.toFixed(2),
7528
- metric.avgDuration.toFixed(2),
7529
- metric.minDuration.toFixed(2),
7530
- metric.maxDuration.toFixed(2),
7531
- metric.stdDev.toFixed(2),
7532
- metric.median.toFixed(2),
7533
- metric.p95.toFixed(2),
7534
- metric.p99.toFixed(2),
7535
- metric.avgWaitTime.toFixed(2),
7536
- metric.minWaitTime.toFixed(2),
7537
- metric.maxWaitTime.toFixed(2),
7538
- ]);
7539
- const summaryTableData = [summaryHeader, summarySeparator, ...summaryRows];
7540
- const summaryTable = summaryTableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7702
+ // Generate summary table using Column interface
7703
+ const visibleColumns = columns$3.filter((col) => col.isVisible());
7704
+ const header = visibleColumns.map((col) => col.label);
7705
+ const separator = visibleColumns.map(() => "---");
7706
+ const rows = sortedMetrics.map((metric) => visibleColumns.map((col) => col.format(metric)));
7707
+ const tableData = [header, separator, ...rows];
7708
+ const summaryTable = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7541
7709
  // Calculate percentage of total time for each metric
7542
7710
  const percentages = sortedMetrics.map((metric) => {
7543
7711
  const pct = (metric.totalDuration / stats.totalDuration) * 100;
@@ -7772,21 +7940,25 @@ function createStrategyColumns(metric) {
7772
7940
  key: "rank",
7773
7941
  label: "Rank",
7774
7942
  format: (data, index) => `${index + 1}`,
7943
+ isVisible: () => true,
7775
7944
  },
7776
7945
  {
7777
7946
  key: "strategy",
7778
7947
  label: "Strategy",
7779
7948
  format: (data) => data.strategyName,
7949
+ isVisible: () => true,
7780
7950
  },
7781
7951
  {
7782
7952
  key: "metric",
7783
7953
  label: metric,
7784
7954
  format: (data) => formatMetric(data.metricValue),
7955
+ isVisible: () => true,
7785
7956
  },
7786
7957
  {
7787
7958
  key: "totalSignals",
7788
7959
  label: "Total Signals",
7789
7960
  format: (data) => `${data.stats.totalSignals}`,
7961
+ isVisible: () => true,
7790
7962
  },
7791
7963
  {
7792
7964
  key: "winRate",
@@ -7794,6 +7966,7 @@ function createStrategyColumns(metric) {
7794
7966
  format: (data) => data.stats.winRate !== null
7795
7967
  ? `${data.stats.winRate.toFixed(2)}%`
7796
7968
  : "N/A",
7969
+ isVisible: () => true,
7797
7970
  },
7798
7971
  {
7799
7972
  key: "avgPnl",
@@ -7801,6 +7974,7 @@ function createStrategyColumns(metric) {
7801
7974
  format: (data) => data.stats.avgPnl !== null
7802
7975
  ? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
7803
7976
  : "N/A",
7977
+ isVisible: () => true,
7804
7978
  },
7805
7979
  {
7806
7980
  key: "totalPnl",
@@ -7808,6 +7982,7 @@ function createStrategyColumns(metric) {
7808
7982
  format: (data) => data.stats.totalPnl !== null
7809
7983
  ? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
7810
7984
  : "N/A",
7985
+ isVisible: () => true,
7811
7986
  },
7812
7987
  {
7813
7988
  key: "sharpeRatio",
@@ -7815,6 +7990,7 @@ function createStrategyColumns(metric) {
7815
7990
  format: (data) => data.stats.sharpeRatio !== null
7816
7991
  ? `${data.stats.sharpeRatio.toFixed(3)}`
7817
7992
  : "N/A",
7993
+ isVisible: () => true,
7818
7994
  },
7819
7995
  {
7820
7996
  key: "stdDev",
@@ -7822,6 +7998,7 @@ function createStrategyColumns(metric) {
7822
7998
  format: (data) => data.stats.stdDev !== null
7823
7999
  ? `${data.stats.stdDev.toFixed(3)}%`
7824
8000
  : "N/A",
8001
+ isVisible: () => true,
7825
8002
  },
7826
8003
  ];
7827
8004
  }
@@ -7834,48 +8011,56 @@ const pnlColumns = [
7834
8011
  key: "strategy",
7835
8012
  label: "Strategy",
7836
8013
  format: (data) => data.strategyName,
8014
+ isVisible: () => true,
7837
8015
  },
7838
8016
  {
7839
8017
  key: "signalId",
7840
8018
  label: "Signal ID",
7841
8019
  format: (data) => data.signalId,
8020
+ isVisible: () => true,
7842
8021
  },
7843
8022
  {
7844
8023
  key: "symbol",
7845
8024
  label: "Symbol",
7846
8025
  format: (data) => data.symbol,
8026
+ isVisible: () => true,
7847
8027
  },
7848
8028
  {
7849
8029
  key: "position",
7850
8030
  label: "Position",
7851
8031
  format: (data) => data.position.toUpperCase(),
8032
+ isVisible: () => true,
7852
8033
  },
7853
8034
  {
7854
8035
  key: "pnl",
7855
8036
  label: "PNL (net)",
7856
8037
  format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
8038
+ isVisible: () => true,
7857
8039
  },
7858
8040
  {
7859
8041
  key: "closeReason",
7860
8042
  label: "Close Reason",
7861
8043
  format: (data) => data.closeReason,
8044
+ isVisible: () => true,
7862
8045
  },
7863
8046
  {
7864
8047
  key: "openTime",
7865
8048
  label: "Open Time",
7866
8049
  format: (data) => new Date(data.openTime).toISOString(),
8050
+ isVisible: () => true,
7867
8051
  },
7868
8052
  {
7869
8053
  key: "closeTime",
7870
8054
  label: "Close Time",
7871
8055
  format: (data) => new Date(data.closeTime).toISOString(),
8056
+ isVisible: () => true,
7872
8057
  },
7873
8058
  ];
7874
8059
  /**
7875
8060
  * Storage class for accumulating walker results.
7876
8061
  * Maintains a list of all strategy results and provides methods to generate reports.
7877
8062
  */
7878
- let ReportStorage$1 = class ReportStorage {
8063
+ let ReportStorage$2 = class ReportStorage {
7879
8064
  constructor(walkerName) {
7880
8065
  this.walkerName = walkerName;
7881
8066
  /** Walker metadata (set from first addResult call) */
@@ -7893,17 +8078,13 @@ let ReportStorage$1 = class ReportStorage {
7893
8078
  * @param data - Walker contract with strategy result
7894
8079
  */
7895
8080
  addResult(data) {
7896
- {
7897
- this._bestMetric = data.bestMetric;
7898
- this._bestStrategy = data.bestStrategy;
7899
- this._totalStrategies = data.totalStrategies;
7900
- }
7901
- // Update best stats only if this strategy is the current best
8081
+ this._totalStrategies = data.totalStrategies;
8082
+ this._bestMetric = data.bestMetric;
8083
+ this._bestStrategy = data.bestStrategy;
7902
8084
  if (data.strategyName === data.bestStrategy) {
7903
8085
  this._bestStats = data.stats;
7904
8086
  }
7905
- // Add strategy result to comparison list
7906
- this._strategyResults.push({
8087
+ this._strategyResults.unshift({
7907
8088
  strategyName: data.strategyName,
7908
8089
  stats: data.stats,
7909
8090
  metricValue: data.metricValue,
@@ -7957,11 +8138,12 @@ let ReportStorage$1 = class ReportStorage {
7957
8138
  const topStrategies = sortedResults.slice(0, topN);
7958
8139
  // Get columns configuration
7959
8140
  const columns = createStrategyColumns(metric);
8141
+ const visibleColumns = columns.filter((col) => col.isVisible());
7960
8142
  // Build table header
7961
- const header = columns.map((col) => col.label);
7962
- const separator = columns.map(() => "---");
8143
+ const header = visibleColumns.map((col) => col.label);
8144
+ const separator = visibleColumns.map(() => "---");
7963
8145
  // Build table rows
7964
- const rows = topStrategies.map((result, index) => columns.map((col) => col.format(result, index)));
8146
+ const rows = topStrategies.map((result, index) => visibleColumns.map((col) => col.format(result, index)));
7965
8147
  const tableData = [header, separator, ...rows];
7966
8148
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7967
8149
  }
@@ -7995,10 +8177,11 @@ let ReportStorage$1 = class ReportStorage {
7995
8177
  return "No closed signals available.";
7996
8178
  }
7997
8179
  // Build table header
7998
- const header = pnlColumns.map((col) => col.label);
7999
- const separator = pnlColumns.map(() => "---");
8180
+ const visibleColumns = pnlColumns.filter((col) => col.isVisible());
8181
+ const header = visibleColumns.map((col) => col.label);
8182
+ const separator = visibleColumns.map(() => "---");
8000
8183
  // Build table rows
8001
- const rows = allSignals.map((signal) => pnlColumns.map((col) => col.format(signal)));
8184
+ const rows = allSignals.map((signal) => visibleColumns.map((col) => col.format(signal)));
8002
8185
  const tableData = [header, separator, ...rows];
8003
8186
  return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8004
8187
  }
@@ -8087,7 +8270,7 @@ class WalkerMarkdownService {
8087
8270
  * Memoized function to get or create ReportStorage for a walker.
8088
8271
  * Each walker gets its own isolated storage instance.
8089
8272
  */
8090
- this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$1(walkerName));
8273
+ this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$2(walkerName));
8091
8274
  /**
8092
8275
  * Processes walker progress events and accumulates strategy results.
8093
8276
  * Should be called from walkerEmitter.
@@ -8258,70 +8441,82 @@ function isUnsafe(value) {
8258
8441
  }
8259
8442
  return false;
8260
8443
  }
8261
- const columns$1 = [
8444
+ const columns$2 = [
8262
8445
  {
8263
8446
  key: "symbol",
8264
8447
  label: "Symbol",
8265
8448
  format: (data) => data.symbol,
8449
+ isVisible: () => true,
8266
8450
  },
8267
8451
  {
8268
8452
  key: "totalPnl",
8269
8453
  label: "Total PNL",
8270
8454
  format: (data) => data.totalPnl !== null ? str(data.totalPnl, "%+.2f%%") : "N/A",
8455
+ isVisible: () => true,
8271
8456
  },
8272
8457
  {
8273
8458
  key: "sharpeRatio",
8274
8459
  label: "Sharpe",
8275
8460
  format: (data) => data.sharpeRatio !== null ? str(data.sharpeRatio, "%.2f") : "N/A",
8461
+ isVisible: () => true,
8276
8462
  },
8277
8463
  {
8278
8464
  key: "profitFactor",
8279
8465
  label: "PF",
8280
8466
  format: (data) => data.profitFactor !== null ? str(data.profitFactor, "%.2f") : "N/A",
8467
+ isVisible: () => true,
8281
8468
  },
8282
8469
  {
8283
8470
  key: "expectancy",
8284
8471
  label: "Expect",
8285
8472
  format: (data) => data.expectancy !== null ? str(data.expectancy, "%+.2f%%") : "N/A",
8473
+ isVisible: () => true,
8286
8474
  },
8287
8475
  {
8288
8476
  key: "winRate",
8289
8477
  label: "WR",
8290
8478
  format: (data) => data.winRate !== null ? str(data.winRate, "%.1f%%") : "N/A",
8479
+ isVisible: () => true,
8291
8480
  },
8292
8481
  {
8293
8482
  key: "avgWin",
8294
8483
  label: "Avg Win",
8295
8484
  format: (data) => data.avgWin !== null ? str(data.avgWin, "%+.2f%%") : "N/A",
8485
+ isVisible: () => true,
8296
8486
  },
8297
8487
  {
8298
8488
  key: "avgLoss",
8299
8489
  label: "Avg Loss",
8300
8490
  format: (data) => data.avgLoss !== null ? str(data.avgLoss, "%+.2f%%") : "N/A",
8491
+ isVisible: () => true,
8301
8492
  },
8302
8493
  {
8303
8494
  key: "maxDrawdown",
8304
8495
  label: "Max DD",
8305
8496
  format: (data) => data.maxDrawdown !== null ? str(-data.maxDrawdown, "%.2f%%") : "N/A",
8497
+ isVisible: () => true,
8306
8498
  },
8307
8499
  {
8308
8500
  key: "maxWinStreak",
8309
8501
  label: "W Streak",
8310
8502
  format: (data) => data.maxWinStreak.toString(),
8503
+ isVisible: () => true,
8311
8504
  },
8312
8505
  {
8313
8506
  key: "maxLossStreak",
8314
8507
  label: "L Streak",
8315
8508
  format: (data) => data.maxLossStreak.toString(),
8509
+ isVisible: () => true,
8316
8510
  },
8317
8511
  {
8318
8512
  key: "totalTrades",
8319
8513
  label: "Trades",
8320
8514
  format: (data) => data.totalTrades.toString(),
8515
+ isVisible: () => true,
8321
8516
  },
8322
8517
  ];
8323
8518
  /** Maximum number of signals to store per symbol in heatmap reports */
8324
- const MAX_EVENTS$1 = 250;
8519
+ const MAX_EVENTS$2 = 250;
8325
8520
  /**
8326
8521
  * Storage class for accumulating closed signals per strategy and generating heatmap.
8327
8522
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -8342,10 +8537,10 @@ class HeatmapStorage {
8342
8537
  this.symbolData.set(symbol, []);
8343
8538
  }
8344
8539
  const signals = this.symbolData.get(symbol);
8345
- signals.push(data);
8540
+ signals.unshift(data);
8346
8541
  // Trim queue if exceeded MAX_EVENTS per symbol
8347
- if (signals.length > MAX_EVENTS$1) {
8348
- signals.shift();
8542
+ if (signals.length > MAX_EVENTS$2) {
8543
+ signals.pop();
8349
8544
  }
8350
8545
  }
8351
8546
  /**
@@ -8565,9 +8760,10 @@ class HeatmapStorage {
8565
8760
  "*No data available*"
8566
8761
  ].join("\n");
8567
8762
  }
8568
- const header = columns$1.map((col) => col.label);
8569
- const separator = columns$1.map(() => "---");
8570
- const rows = data.symbols.map((row) => columns$1.map((col) => col.format(row)));
8763
+ const visibleColumns = columns$2.filter((col) => col.isVisible());
8764
+ const header = visibleColumns.map((col) => col.label);
8765
+ const separator = visibleColumns.map(() => "---");
8766
+ const rows = data.symbols.map((row) => visibleColumns.map((col) => col.format(row)));
8571
8767
  const tableData = [header, separator, ...rows];
8572
8768
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8573
8769
  return [
@@ -10605,7 +10801,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
10605
10801
  revenuePercent,
10606
10802
  backtest,
10607
10803
  });
10608
- await self.params.onProfit(symbol, data, currentPrice, level, backtest, when.getTime());
10804
+ await self.params.onProfit(symbol, data.strategyName, data.exchangeName, data, currentPrice, level, backtest, when.getTime());
10609
10805
  }
10610
10806
  }
10611
10807
  if (shouldPersist) {
@@ -10652,7 +10848,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
10652
10848
  lossPercent,
10653
10849
  backtest,
10654
10850
  });
10655
- await self.params.onLoss(symbol, data, currentPrice, level, backtest, when.getTime());
10851
+ await self.params.onLoss(symbol, data.strategyName, data.exchangeName, data, currentPrice, level, backtest, when.getTime());
10656
10852
  }
10657
10853
  }
10658
10854
  if (shouldPersist) {
@@ -10923,6 +11119,7 @@ class ClientPartial {
10923
11119
  symbol,
10924
11120
  data,
10925
11121
  priceClose,
11122
+ backtest,
10926
11123
  });
10927
11124
  if (this._states === NEED_FETCH) {
10928
11125
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
@@ -10939,14 +11136,18 @@ class ClientPartial {
10939
11136
  * Emits PartialProfitContract event to all subscribers.
10940
11137
  *
10941
11138
  * @param symbol - Trading pair symbol
11139
+ * @param strategyName - Strategy name that generated this signal
11140
+ * @param exchangeName - Exchange name where this signal is being executed
10942
11141
  * @param data - Signal row data
10943
11142
  * @param currentPrice - Current market price
10944
11143
  * @param level - Profit level reached
10945
11144
  * @param backtest - True if backtest mode
10946
11145
  * @param timestamp - Event timestamp in milliseconds
10947
11146
  */
10948
- const COMMIT_PROFIT_FN = async (symbol, data, currentPrice, level, backtest, timestamp) => await partialProfitSubject.next({
11147
+ const COMMIT_PROFIT_FN = async (symbol, strategyName, exchangeName, data, currentPrice, level, backtest, timestamp) => await partialProfitSubject.next({
10949
11148
  symbol,
11149
+ strategyName,
11150
+ exchangeName,
10950
11151
  data,
10951
11152
  currentPrice,
10952
11153
  level,
@@ -10960,14 +11161,18 @@ const COMMIT_PROFIT_FN = async (symbol, data, currentPrice, level, backtest, tim
10960
11161
  * Emits PartialLossContract event to all subscribers.
10961
11162
  *
10962
11163
  * @param symbol - Trading pair symbol
11164
+ * @param strategyName - Strategy name that generated this signal
11165
+ * @param exchangeName - Exchange name where this signal is being executed
10963
11166
  * @param data - Signal row data
10964
11167
  * @param currentPrice - Current market price
10965
11168
  * @param level - Loss level reached
10966
11169
  * @param backtest - True if backtest mode
10967
11170
  * @param timestamp - Event timestamp in milliseconds
10968
11171
  */
10969
- const COMMIT_LOSS_FN = async (symbol, data, currentPrice, level, backtest, timestamp) => await partialLossSubject.next({
11172
+ const COMMIT_LOSS_FN = async (symbol, strategyName, exchangeName, data, currentPrice, level, backtest, timestamp) => await partialLossSubject.next({
10970
11173
  symbol,
11174
+ strategyName,
11175
+ exchangeName,
10971
11176
  data,
10972
11177
  currentPrice,
10973
11178
  level,
@@ -11113,60 +11318,69 @@ class PartialConnectionService {
11113
11318
  }
11114
11319
  }
11115
11320
 
11116
- const columns = [
11321
+ const columns$1 = [
11117
11322
  {
11118
11323
  key: "action",
11119
11324
  label: "Action",
11120
11325
  format: (data) => data.action.toUpperCase(),
11326
+ isVisible: () => true,
11121
11327
  },
11122
11328
  {
11123
11329
  key: "symbol",
11124
11330
  label: "Symbol",
11125
11331
  format: (data) => data.symbol,
11332
+ isVisible: () => true,
11126
11333
  },
11127
11334
  {
11128
11335
  key: "strategyName",
11129
11336
  label: "Strategy",
11130
11337
  format: (data) => data.strategyName,
11338
+ isVisible: () => true,
11131
11339
  },
11132
11340
  {
11133
11341
  key: "signalId",
11134
11342
  label: "Signal ID",
11135
11343
  format: (data) => data.signalId,
11344
+ isVisible: () => true,
11136
11345
  },
11137
11346
  {
11138
11347
  key: "position",
11139
11348
  label: "Position",
11140
11349
  format: (data) => data.position.toUpperCase(),
11350
+ isVisible: () => true,
11141
11351
  },
11142
11352
  {
11143
11353
  key: "level",
11144
11354
  label: "Level %",
11145
11355
  format: (data) => data.action === "profit" ? `+${data.level}%` : `-${data.level}%`,
11356
+ isVisible: () => true,
11146
11357
  },
11147
11358
  {
11148
11359
  key: "currentPrice",
11149
11360
  label: "Current Price",
11150
11361
  format: (data) => `${data.currentPrice.toFixed(8)} USD`,
11362
+ isVisible: () => true,
11151
11363
  },
11152
11364
  {
11153
11365
  key: "timestamp",
11154
11366
  label: "Timestamp",
11155
11367
  format: (data) => new Date(data.timestamp).toISOString(),
11368
+ isVisible: () => true,
11156
11369
  },
11157
11370
  {
11158
11371
  key: "mode",
11159
11372
  label: "Mode",
11160
11373
  format: (data) => (data.backtest ? "Backtest" : "Live"),
11374
+ isVisible: () => true,
11161
11375
  },
11162
11376
  ];
11163
11377
  /** Maximum number of events to store in partial reports */
11164
- const MAX_EVENTS = 250;
11378
+ const MAX_EVENTS$1 = 250;
11165
11379
  /**
11166
11380
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
11167
11381
  * Maintains a chronological list of profit and loss level events.
11168
11382
  */
11169
- class ReportStorage {
11383
+ let ReportStorage$1 = class ReportStorage {
11170
11384
  constructor() {
11171
11385
  /** Internal list of all partial events for this symbol */
11172
11386
  this._eventList = [];
@@ -11180,7 +11394,7 @@ class ReportStorage {
11180
11394
  * @param backtest - True if backtest mode
11181
11395
  */
11182
11396
  addProfitEvent(data, currentPrice, level, backtest, timestamp) {
11183
- this._eventList.push({
11397
+ this._eventList.unshift({
11184
11398
  timestamp,
11185
11399
  action: "profit",
11186
11400
  symbol: data.symbol,
@@ -11192,8 +11406,8 @@ class ReportStorage {
11192
11406
  backtest,
11193
11407
  });
11194
11408
  // Trim queue if exceeded MAX_EVENTS
11195
- if (this._eventList.length > MAX_EVENTS) {
11196
- this._eventList.shift();
11409
+ if (this._eventList.length > MAX_EVENTS$1) {
11410
+ this._eventList.pop();
11197
11411
  }
11198
11412
  }
11199
11413
  /**
@@ -11205,7 +11419,7 @@ class ReportStorage {
11205
11419
  * @param backtest - True if backtest mode
11206
11420
  */
11207
11421
  addLossEvent(data, currentPrice, level, backtest, timestamp) {
11208
- this._eventList.push({
11422
+ this._eventList.unshift({
11209
11423
  timestamp,
11210
11424
  action: "loss",
11211
11425
  symbol: data.symbol,
@@ -11217,8 +11431,8 @@ class ReportStorage {
11217
11431
  backtest,
11218
11432
  });
11219
11433
  // Trim queue if exceeded MAX_EVENTS
11220
- if (this._eventList.length > MAX_EVENTS) {
11221
- this._eventList.shift();
11434
+ if (this._eventList.length > MAX_EVENTS$1) {
11435
+ this._eventList.pop();
11222
11436
  }
11223
11437
  }
11224
11438
  /**
@@ -11260,9 +11474,10 @@ class ReportStorage {
11260
11474
  "No partial profit/loss events recorded yet."
11261
11475
  ].join("\n");
11262
11476
  }
11263
- const header = columns.map((col) => col.label);
11264
- const separator = columns.map(() => "---");
11265
- const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
11477
+ const visibleColumns = columns$1.filter((col) => col.isVisible());
11478
+ const header = visibleColumns.map((col) => col.label);
11479
+ const separator = visibleColumns.map(() => "---");
11480
+ const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
11266
11481
  const tableData = [header, separator, ...rows];
11267
11482
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
11268
11483
  return [
@@ -11296,7 +11511,7 @@ class ReportStorage {
11296
11511
  console.error(`Failed to save markdown report:`, error);
11297
11512
  }
11298
11513
  }
11299
- }
11514
+ };
11300
11515
  /**
11301
11516
  * Service for generating and saving partial profit/loss markdown reports.
11302
11517
  *
@@ -11326,7 +11541,7 @@ class PartialMarkdownService {
11326
11541
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
11327
11542
  * Each symbol-strategy combination gets its own isolated storage instance.
11328
11543
  */
11329
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage());
11544
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$1());
11330
11545
  /**
11331
11546
  * Processes profit events and accumulates them.
11332
11547
  * Should be called from partialProfitSubject subscription.
@@ -11636,7 +11851,7 @@ class PartialGlobalService {
11636
11851
  * Warning threshold for message size in kilobytes.
11637
11852
  * Messages exceeding this size trigger console warnings.
11638
11853
  */
11639
- const WARN_KB = 100;
11854
+ const WARN_KB = 20;
11640
11855
  /**
11641
11856
  * Internal function for dumping signal data to markdown files.
11642
11857
  * Creates a directory structure with system prompts, user messages, and LLM output.
@@ -11898,6 +12113,372 @@ class ConfigValidationService {
11898
12113
  }
11899
12114
  }
11900
12115
 
12116
+ const columns = [
12117
+ {
12118
+ key: "symbol",
12119
+ label: "Symbol",
12120
+ format: (data) => data.symbol,
12121
+ isVisible: () => true,
12122
+ },
12123
+ {
12124
+ key: "strategyName",
12125
+ label: "Strategy",
12126
+ format: (data) => data.strategyName,
12127
+ isVisible: () => true,
12128
+ },
12129
+ {
12130
+ key: "signalId",
12131
+ label: "Signal ID",
12132
+ format: (data) => data.pendingSignal.id || "N/A",
12133
+ isVisible: () => true,
12134
+ },
12135
+ {
12136
+ key: "position",
12137
+ label: "Position",
12138
+ format: (data) => data.pendingSignal.position.toUpperCase(),
12139
+ isVisible: () => true,
12140
+ },
12141
+ {
12142
+ key: "note",
12143
+ label: "Note",
12144
+ format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
12145
+ isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
12146
+ },
12147
+ {
12148
+ key: "exchangeName",
12149
+ label: "Exchange",
12150
+ format: (data) => data.exchangeName,
12151
+ isVisible: () => true,
12152
+ },
12153
+ {
12154
+ key: "openPrice",
12155
+ label: "Open Price",
12156
+ format: (data) => data.pendingSignal.priceOpen !== undefined
12157
+ ? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
12158
+ : "N/A",
12159
+ isVisible: () => true,
12160
+ },
12161
+ {
12162
+ key: "takeProfit",
12163
+ label: "Take Profit",
12164
+ format: (data) => data.pendingSignal.priceTakeProfit !== undefined
12165
+ ? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
12166
+ : "N/A",
12167
+ isVisible: () => true,
12168
+ },
12169
+ {
12170
+ key: "stopLoss",
12171
+ label: "Stop Loss",
12172
+ format: (data) => data.pendingSignal.priceStopLoss !== undefined
12173
+ ? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
12174
+ : "N/A",
12175
+ isVisible: () => true,
12176
+ },
12177
+ {
12178
+ key: "currentPrice",
12179
+ label: "Current Price",
12180
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
12181
+ isVisible: () => true,
12182
+ },
12183
+ {
12184
+ key: "activePositionCount",
12185
+ label: "Active Positions",
12186
+ format: (data) => data.activePositionCount.toString(),
12187
+ isVisible: () => true,
12188
+ },
12189
+ {
12190
+ key: "comment",
12191
+ label: "Reason",
12192
+ format: (data) => data.comment,
12193
+ isVisible: () => true,
12194
+ },
12195
+ {
12196
+ key: "timestamp",
12197
+ label: "Timestamp",
12198
+ format: (data) => new Date(data.timestamp).toISOString(),
12199
+ isVisible: () => true,
12200
+ },
12201
+ ];
12202
+ /** Maximum number of events to store in risk reports */
12203
+ const MAX_EVENTS = 250;
12204
+ /**
12205
+ * Storage class for accumulating risk rejection events per symbol-strategy pair.
12206
+ * Maintains a chronological list of rejected signals due to risk limits.
12207
+ */
12208
+ class ReportStorage {
12209
+ constructor() {
12210
+ /** Internal list of all risk rejection events for this symbol */
12211
+ this._eventList = [];
12212
+ }
12213
+ /**
12214
+ * Adds a risk rejection event to the storage.
12215
+ *
12216
+ * @param event - Risk rejection event data
12217
+ */
12218
+ addRejectionEvent(event) {
12219
+ this._eventList.unshift(event);
12220
+ // Trim queue if exceeded MAX_EVENTS
12221
+ if (this._eventList.length > MAX_EVENTS) {
12222
+ this._eventList.pop();
12223
+ }
12224
+ }
12225
+ /**
12226
+ * Calculates statistical data from risk rejection events (Controller).
12227
+ *
12228
+ * @returns Statistical data (empty object if no events)
12229
+ */
12230
+ async getData() {
12231
+ if (this._eventList.length === 0) {
12232
+ return {
12233
+ eventList: [],
12234
+ totalRejections: 0,
12235
+ bySymbol: {},
12236
+ byStrategy: {},
12237
+ };
12238
+ }
12239
+ const bySymbol = {};
12240
+ const byStrategy = {};
12241
+ for (const event of this._eventList) {
12242
+ bySymbol[event.symbol] = (bySymbol[event.symbol] || 0) + 1;
12243
+ byStrategy[event.strategyName] = (byStrategy[event.strategyName] || 0) + 1;
12244
+ }
12245
+ return {
12246
+ eventList: this._eventList,
12247
+ totalRejections: this._eventList.length,
12248
+ bySymbol,
12249
+ byStrategy,
12250
+ };
12251
+ }
12252
+ /**
12253
+ * Generates markdown report with all risk rejection events for a symbol-strategy pair (View).
12254
+ *
12255
+ * @param symbol - Trading pair symbol
12256
+ * @param strategyName - Strategy name
12257
+ * @returns Markdown formatted report with all events
12258
+ */
12259
+ async getReport(symbol, strategyName) {
12260
+ const stats = await this.getData();
12261
+ if (stats.totalRejections === 0) {
12262
+ return [
12263
+ `# Risk Rejection Report: ${symbol}:${strategyName}`,
12264
+ "",
12265
+ "No risk rejections recorded yet.",
12266
+ ].join("\n");
12267
+ }
12268
+ const visibleColumns = columns.filter((col) => col.isVisible());
12269
+ const header = visibleColumns.map((col) => col.label);
12270
+ const separator = visibleColumns.map(() => "---");
12271
+ const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
12272
+ const tableData = [header, separator, ...rows];
12273
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
12274
+ return [
12275
+ `# Risk Rejection Report: ${symbol}:${strategyName}`,
12276
+ "",
12277
+ table,
12278
+ "",
12279
+ `**Total rejections:** ${stats.totalRejections}`,
12280
+ "",
12281
+ "## Rejections by Symbol",
12282
+ ...Object.entries(stats.bySymbol).map(([sym, count]) => `- ${sym}: ${count}`),
12283
+ "",
12284
+ "## Rejections by Strategy",
12285
+ ...Object.entries(stats.byStrategy).map(([strat, count]) => `- ${strat}: ${count}`),
12286
+ ].join("\n");
12287
+ }
12288
+ /**
12289
+ * Saves symbol-strategy report to disk.
12290
+ *
12291
+ * @param symbol - Trading pair symbol
12292
+ * @param strategyName - Strategy name
12293
+ * @param path - Directory path to save report (default: "./dump/risk")
12294
+ */
12295
+ async dump(symbol, strategyName, path = "./dump/risk") {
12296
+ const markdown = await this.getReport(symbol, strategyName);
12297
+ try {
12298
+ const dir = join(process.cwd(), path);
12299
+ await mkdir(dir, { recursive: true });
12300
+ const filename = `${symbol}_${strategyName}.md`;
12301
+ const filepath = join(dir, filename);
12302
+ await writeFile(filepath, markdown, "utf-8");
12303
+ console.log(`Risk rejection report saved: ${filepath}`);
12304
+ }
12305
+ catch (error) {
12306
+ console.error(`Failed to save markdown report:`, error);
12307
+ }
12308
+ }
12309
+ }
12310
+ /**
12311
+ * Service for generating and saving risk rejection markdown reports.
12312
+ *
12313
+ * Features:
12314
+ * - Listens to risk rejection events via riskSubject
12315
+ * - Accumulates all rejection events per symbol-strategy pair
12316
+ * - Generates markdown tables with detailed rejection information
12317
+ * - Provides statistics (total rejections, by symbol, by strategy)
12318
+ * - Saves reports to disk in dump/risk/{symbol}_{strategyName}.md
12319
+ *
12320
+ * @example
12321
+ * ```typescript
12322
+ * const service = new RiskMarkdownService();
12323
+ *
12324
+ * // Service automatically subscribes to subjects on init
12325
+ * // No manual callback setup needed
12326
+ *
12327
+ * // Later: generate and save report
12328
+ * await service.dump("BTCUSDT", "my-strategy");
12329
+ * ```
12330
+ */
12331
+ class RiskMarkdownService {
12332
+ constructor() {
12333
+ /** Logger service for debug output */
12334
+ this.loggerService = inject(TYPES.loggerService);
12335
+ /**
12336
+ * Memoized function to get or create ReportStorage for a symbol-strategy pair.
12337
+ * Each symbol-strategy combination gets its own isolated storage instance.
12338
+ */
12339
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage());
12340
+ /**
12341
+ * Processes risk rejection events and accumulates them.
12342
+ * Should be called from riskSubject subscription.
12343
+ *
12344
+ * @param data - Risk rejection event data
12345
+ *
12346
+ * @example
12347
+ * ```typescript
12348
+ * const service = new RiskMarkdownService();
12349
+ * // Service automatically subscribes in init()
12350
+ * ```
12351
+ */
12352
+ this.tickRejection = async (data) => {
12353
+ this.loggerService.log("riskMarkdownService tickRejection", {
12354
+ data,
12355
+ });
12356
+ const storage = this.getStorage(data.symbol, data.strategyName);
12357
+ storage.addRejectionEvent(data);
12358
+ };
12359
+ /**
12360
+ * Gets statistical data from all risk rejection events for a symbol-strategy pair.
12361
+ * Delegates to ReportStorage.getData().
12362
+ *
12363
+ * @param symbol - Trading pair symbol to get data for
12364
+ * @param strategyName - Strategy name to get data for
12365
+ * @returns Statistical data object with all metrics
12366
+ *
12367
+ * @example
12368
+ * ```typescript
12369
+ * const service = new RiskMarkdownService();
12370
+ * const stats = await service.getData("BTCUSDT", "my-strategy");
12371
+ * console.log(stats.totalRejections, stats.bySymbol);
12372
+ * ```
12373
+ */
12374
+ this.getData = async (symbol, strategyName) => {
12375
+ this.loggerService.log("riskMarkdownService getData", {
12376
+ symbol,
12377
+ strategyName,
12378
+ });
12379
+ const storage = this.getStorage(symbol, strategyName);
12380
+ return storage.getData();
12381
+ };
12382
+ /**
12383
+ * Generates markdown report with all risk rejection events for a symbol-strategy pair.
12384
+ * Delegates to ReportStorage.getReport().
12385
+ *
12386
+ * @param symbol - Trading pair symbol to generate report for
12387
+ * @param strategyName - Strategy name to generate report for
12388
+ * @returns Markdown formatted report string with table of all events
12389
+ *
12390
+ * @example
12391
+ * ```typescript
12392
+ * const service = new RiskMarkdownService();
12393
+ * const markdown = await service.getReport("BTCUSDT", "my-strategy");
12394
+ * console.log(markdown);
12395
+ * ```
12396
+ */
12397
+ this.getReport = async (symbol, strategyName) => {
12398
+ this.loggerService.log("riskMarkdownService getReport", {
12399
+ symbol,
12400
+ strategyName,
12401
+ });
12402
+ const storage = this.getStorage(symbol, strategyName);
12403
+ return storage.getReport(symbol, strategyName);
12404
+ };
12405
+ /**
12406
+ * Saves symbol-strategy report to disk.
12407
+ * Creates directory if it doesn't exist.
12408
+ * Delegates to ReportStorage.dump().
12409
+ *
12410
+ * @param symbol - Trading pair symbol to save report for
12411
+ * @param strategyName - Strategy name to save report for
12412
+ * @param path - Directory path to save report (default: "./dump/risk")
12413
+ *
12414
+ * @example
12415
+ * ```typescript
12416
+ * const service = new RiskMarkdownService();
12417
+ *
12418
+ * // Save to default path: ./dump/risk/BTCUSDT_my-strategy.md
12419
+ * await service.dump("BTCUSDT", "my-strategy");
12420
+ *
12421
+ * // Save to custom path: ./custom/path/BTCUSDT_my-strategy.md
12422
+ * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
12423
+ * ```
12424
+ */
12425
+ this.dump = async (symbol, strategyName, path = "./dump/risk") => {
12426
+ this.loggerService.log("riskMarkdownService dump", {
12427
+ symbol,
12428
+ strategyName,
12429
+ path,
12430
+ });
12431
+ const storage = this.getStorage(symbol, strategyName);
12432
+ await storage.dump(symbol, strategyName, path);
12433
+ };
12434
+ /**
12435
+ * Clears accumulated event data from storage.
12436
+ * If ctx is provided, clears only that specific symbol-strategy pair's data.
12437
+ * If nothing is provided, clears all data.
12438
+ *
12439
+ * @param ctx - Optional context with symbol and strategyName
12440
+ *
12441
+ * @example
12442
+ * ```typescript
12443
+ * const service = new RiskMarkdownService();
12444
+ *
12445
+ * // Clear specific symbol-strategy pair
12446
+ * await service.clear({ symbol: "BTCUSDT", strategyName: "my-strategy" });
12447
+ *
12448
+ * // Clear all data
12449
+ * await service.clear();
12450
+ * ```
12451
+ */
12452
+ this.clear = async (ctx) => {
12453
+ this.loggerService.log("riskMarkdownService clear", {
12454
+ ctx,
12455
+ });
12456
+ if (ctx) {
12457
+ const key = `${ctx.symbol}:${ctx.strategyName}`;
12458
+ this.getStorage.clear(key);
12459
+ }
12460
+ else {
12461
+ this.getStorage.clear();
12462
+ }
12463
+ };
12464
+ /**
12465
+ * Initializes the service by subscribing to risk rejection events.
12466
+ * Uses singleshot to ensure initialization happens only once.
12467
+ * Automatically called on first use.
12468
+ *
12469
+ * @example
12470
+ * ```typescript
12471
+ * const service = new RiskMarkdownService();
12472
+ * await service.init(); // Subscribe to rejection events
12473
+ * ```
12474
+ */
12475
+ this.init = singleshot(async () => {
12476
+ this.loggerService.log("riskMarkdownService init");
12477
+ riskSubject.subscribe(this.tickRejection);
12478
+ });
12479
+ }
12480
+ }
12481
+
11901
12482
  {
11902
12483
  provide(TYPES.loggerService, () => new LoggerService());
11903
12484
  }
@@ -11958,6 +12539,7 @@ class ConfigValidationService {
11958
12539
  provide(TYPES.heatMarkdownService, () => new HeatMarkdownService());
11959
12540
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
11960
12541
  provide(TYPES.outlineMarkdownService, () => new OutlineMarkdownService());
12542
+ provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
11961
12543
  }
11962
12544
  {
11963
12545
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -12033,6 +12615,7 @@ const markdownServices = {
12033
12615
  heatMarkdownService: inject(TYPES.heatMarkdownService),
12034
12616
  partialMarkdownService: inject(TYPES.partialMarkdownService),
12035
12617
  outlineMarkdownService: inject(TYPES.outlineMarkdownService),
12618
+ riskMarkdownService: inject(TYPES.riskMarkdownService),
12036
12619
  };
12037
12620
  const validationServices = {
12038
12621
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -12807,6 +13390,8 @@ const LISTEN_PARTIAL_PROFIT_METHOD_NAME = "event.listenPartialProfit";
12807
13390
  const LISTEN_PARTIAL_PROFIT_ONCE_METHOD_NAME = "event.listenPartialProfitOnce";
12808
13391
  const LISTEN_PARTIAL_LOSS_METHOD_NAME = "event.listenPartialLoss";
12809
13392
  const LISTEN_PARTIAL_LOSS_ONCE_METHOD_NAME = "event.listenPartialLossOnce";
13393
+ const LISTEN_RISK_METHOD_NAME = "event.listenRisk";
13394
+ const LISTEN_RISK_ONCE_METHOD_NAME = "event.listenRiskOnce";
12810
13395
  /**
12811
13396
  * Subscribes to all signal events with queued async processing.
12812
13397
  *
@@ -13605,6 +14190,75 @@ function listenPartialLossOnce(filterFn, fn) {
13605
14190
  backtest$1.loggerService.log(LISTEN_PARTIAL_LOSS_ONCE_METHOD_NAME);
13606
14191
  return partialLossSubject.filter(filterFn).once(fn);
13607
14192
  }
14193
+ /**
14194
+ * Subscribes to risk rejection events with queued async processing.
14195
+ *
14196
+ * Emits ONLY when a signal is rejected due to risk validation failure.
14197
+ * Does not emit for allowed signals (prevents spam).
14198
+ * Events are processed sequentially in order received, even if callback is async.
14199
+ * Uses queued wrapper to prevent concurrent execution of the callback.
14200
+ *
14201
+ * @param fn - Callback function to handle risk rejection events
14202
+ * @returns Unsubscribe function to stop listening to events
14203
+ *
14204
+ * @example
14205
+ * ```typescript
14206
+ * import { listenRisk } from "./function/event";
14207
+ *
14208
+ * const unsubscribe = listenRisk((event) => {
14209
+ * console.log(`[RISK REJECTED] Signal for ${event.symbol}`);
14210
+ * console.log(`Strategy: ${event.strategyName}`);
14211
+ * console.log(`Position: ${event.pendingSignal.position}`);
14212
+ * console.log(`Active positions: ${event.activePositionCount}`);
14213
+ * console.log(`Reason: ${event.comment}`);
14214
+ * console.log(`Price: ${event.currentPrice}`);
14215
+ * });
14216
+ *
14217
+ * // Later: stop listening
14218
+ * unsubscribe();
14219
+ * ```
14220
+ */
14221
+ function listenRisk(fn) {
14222
+ backtest$1.loggerService.log(LISTEN_RISK_METHOD_NAME);
14223
+ return riskSubject.subscribe(queued(async (event) => fn(event)));
14224
+ }
14225
+ /**
14226
+ * Subscribes to filtered risk rejection events with one-time execution.
14227
+ *
14228
+ * Listens for events matching the filter predicate, then executes callback once
14229
+ * and automatically unsubscribes. Useful for waiting for specific risk rejection conditions.
14230
+ *
14231
+ * @param filterFn - Predicate to filter which events trigger the callback
14232
+ * @param fn - Callback function to handle the filtered event (called only once)
14233
+ * @returns Unsubscribe function to cancel the listener before it fires
14234
+ *
14235
+ * @example
14236
+ * ```typescript
14237
+ * import { listenRiskOnce } from "./function/event";
14238
+ *
14239
+ * // Wait for first risk rejection on BTCUSDT
14240
+ * listenRiskOnce(
14241
+ * (event) => event.symbol === "BTCUSDT",
14242
+ * (event) => {
14243
+ * console.log("BTCUSDT signal rejected!");
14244
+ * console.log("Reason:", event.comment);
14245
+ * }
14246
+ * );
14247
+ *
14248
+ * // Wait for rejection due to position limit
14249
+ * const cancel = listenRiskOnce(
14250
+ * (event) => event.comment.includes("Max") && event.activePositionCount >= 3,
14251
+ * (event) => console.log("Position limit reached:", event.activePositionCount)
14252
+ * );
14253
+ *
14254
+ * // Cancel if needed before event fires
14255
+ * cancel();
14256
+ * ```
14257
+ */
14258
+ function listenRiskOnce(filterFn, fn) {
14259
+ backtest$1.loggerService.log(LISTEN_RISK_ONCE_METHOD_NAME);
14260
+ return riskSubject.filter(filterFn).once(fn);
14261
+ }
13608
14262
 
13609
14263
  const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
13610
14264
  const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
@@ -16229,4 +16883,184 @@ class ConstantUtils {
16229
16883
  */
16230
16884
  const Constant = new ConstantUtils();
16231
16885
 
16232
- export { Backtest, Constant, ExecutionContextService, Heat, Live, MethodContextService, Optimizer, Partial, Performance, PersistBase, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, 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, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, setConfig, setLogger };
16886
+ const RISK_METHOD_NAME_GET_DATA = "RiskUtils.getData";
16887
+ const RISK_METHOD_NAME_GET_REPORT = "RiskUtils.getReport";
16888
+ const RISK_METHOD_NAME_DUMP = "RiskUtils.dump";
16889
+ /**
16890
+ * Utility class for accessing risk rejection reports and statistics.
16891
+ *
16892
+ * Provides static-like methods (via singleton instance) to retrieve data
16893
+ * accumulated by RiskMarkdownService from risk rejection events.
16894
+ *
16895
+ * Features:
16896
+ * - Statistical data extraction (total rejections count, by symbol, by strategy)
16897
+ * - Markdown report generation with event tables
16898
+ * - File export to disk
16899
+ *
16900
+ * Data source:
16901
+ * - RiskMarkdownService listens to riskSubject
16902
+ * - Accumulates rejection events in ReportStorage (max 250 events per symbol-strategy pair)
16903
+ * - Events include: timestamp, symbol, strategyName, position, exchangeName, price, activePositionCount, comment
16904
+ *
16905
+ * @example
16906
+ * ```typescript
16907
+ * import { Risk } from "./classes/Risk";
16908
+ *
16909
+ * // Get statistical data for BTCUSDT:my-strategy
16910
+ * const stats = await Risk.getData("BTCUSDT", "my-strategy");
16911
+ * console.log(`Total rejections: ${stats.totalRejections}`);
16912
+ * console.log(`By symbol:`, stats.bySymbol);
16913
+ * console.log(`By strategy:`, stats.byStrategy);
16914
+ *
16915
+ * // Generate markdown report
16916
+ * const markdown = await Risk.getReport("BTCUSDT", "my-strategy");
16917
+ * console.log(markdown); // Formatted table with all rejection events
16918
+ *
16919
+ * // Export report to file
16920
+ * await Risk.dump("BTCUSDT", "my-strategy"); // Saves to ./dump/risk/BTCUSDT_my-strategy.md
16921
+ * await Risk.dump("BTCUSDT", "my-strategy", "./custom/path"); // Custom directory
16922
+ * ```
16923
+ */
16924
+ class RiskUtils {
16925
+ constructor() {
16926
+ /**
16927
+ * Retrieves statistical data from accumulated risk rejection events.
16928
+ *
16929
+ * Delegates to RiskMarkdownService.getData() which reads from ReportStorage.
16930
+ * Returns aggregated metrics calculated from all rejection events.
16931
+ *
16932
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
16933
+ * @param strategyName - Strategy name (e.g., "my-strategy")
16934
+ * @returns Promise resolving to RiskStatistics object with counts and event list
16935
+ *
16936
+ * @example
16937
+ * ```typescript
16938
+ * const stats = await Risk.getData("BTCUSDT", "my-strategy");
16939
+ *
16940
+ * console.log(`Total rejections: ${stats.totalRejections}`);
16941
+ * console.log(`Rejections by symbol:`, stats.bySymbol);
16942
+ * console.log(`Rejections by strategy:`, stats.byStrategy);
16943
+ *
16944
+ * // Iterate through all rejection events
16945
+ * for (const event of stats.eventList) {
16946
+ * console.log(`REJECTED: ${event.symbol} - ${event.comment} (${event.activePositionCount} active)`);
16947
+ * }
16948
+ * ```
16949
+ */
16950
+ this.getData = async (symbol, strategyName) => {
16951
+ backtest$1.loggerService.info(RISK_METHOD_NAME_GET_DATA, { symbol, strategyName });
16952
+ backtest$1.strategyValidationService.validate(strategyName, RISK_METHOD_NAME_GET_DATA);
16953
+ {
16954
+ const { riskName } = backtest$1.strategySchemaService.get(strategyName);
16955
+ riskName && backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_DATA);
16956
+ }
16957
+ return await backtest$1.riskMarkdownService.getData(symbol, strategyName);
16958
+ };
16959
+ /**
16960
+ * Generates markdown report with all risk rejection events for a symbol-strategy pair.
16961
+ *
16962
+ * Creates formatted table containing:
16963
+ * - Symbol
16964
+ * - Strategy
16965
+ * - Position (LONG/SHORT)
16966
+ * - Exchange
16967
+ * - Price
16968
+ * - Active Positions (at rejection time)
16969
+ * - Reason (from validation note)
16970
+ * - Timestamp (ISO 8601)
16971
+ *
16972
+ * Also includes summary statistics at the end (total rejections, by symbol, by strategy).
16973
+ *
16974
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
16975
+ * @param strategyName - Strategy name (e.g., "my-strategy")
16976
+ * @returns Promise resolving to markdown formatted report string
16977
+ *
16978
+ * @example
16979
+ * ```typescript
16980
+ * const markdown = await Risk.getReport("BTCUSDT", "my-strategy");
16981
+ * console.log(markdown);
16982
+ *
16983
+ * // Output:
16984
+ * // # Risk Rejection Report: BTCUSDT:my-strategy
16985
+ * //
16986
+ * // | Symbol | Strategy | Position | Exchange | Price | Active Positions | Reason | Timestamp |
16987
+ * // | --- | --- | --- | --- | --- | --- | --- | --- |
16988
+ * // | BTCUSDT | my-strategy | LONG | binance | 50000.00000000 USD | 3 | Max 3 positions allowed | 2024-01-15T10:30:00.000Z |
16989
+ * //
16990
+ * // **Total rejections:** 1
16991
+ * //
16992
+ * // ## Rejections by Symbol
16993
+ * // - BTCUSDT: 1
16994
+ * //
16995
+ * // ## Rejections by Strategy
16996
+ * // - my-strategy: 1
16997
+ * ```
16998
+ */
16999
+ this.getReport = async (symbol, strategyName) => {
17000
+ backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, { symbol, strategyName });
17001
+ backtest$1.strategyValidationService.validate(strategyName, RISK_METHOD_NAME_GET_REPORT);
17002
+ {
17003
+ const { riskName } = backtest$1.strategySchemaService.get(strategyName);
17004
+ riskName && backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
17005
+ }
17006
+ return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
17007
+ };
17008
+ /**
17009
+ * Generates and saves markdown report to file.
17010
+ *
17011
+ * Creates directory if it doesn't exist.
17012
+ * Filename format: {symbol}_{strategyName}.md (e.g., "BTCUSDT_my-strategy.md")
17013
+ *
17014
+ * Delegates to RiskMarkdownService.dump() which:
17015
+ * 1. Generates markdown report via getReport()
17016
+ * 2. Creates output directory (recursive mkdir)
17017
+ * 3. Writes file with UTF-8 encoding
17018
+ * 4. Logs success/failure to console
17019
+ *
17020
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
17021
+ * @param strategyName - Strategy name (e.g., "my-strategy")
17022
+ * @param path - Output directory path (default: "./dump/risk")
17023
+ * @returns Promise that resolves when file is written
17024
+ *
17025
+ * @example
17026
+ * ```typescript
17027
+ * // Save to default path: ./dump/risk/BTCUSDT_my-strategy.md
17028
+ * await Risk.dump("BTCUSDT", "my-strategy");
17029
+ *
17030
+ * // Save to custom path: ./reports/risk/BTCUSDT_my-strategy.md
17031
+ * await Risk.dump("BTCUSDT", "my-strategy", "./reports/risk");
17032
+ *
17033
+ * // After multiple symbols backtested, export all risk reports
17034
+ * for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
17035
+ * await Risk.dump(symbol, "my-strategy", "./backtest-results");
17036
+ * }
17037
+ * ```
17038
+ */
17039
+ this.dump = async (symbol, strategyName, path) => {
17040
+ backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, { symbol, strategyName, path });
17041
+ backtest$1.strategyValidationService.validate(strategyName, RISK_METHOD_NAME_DUMP);
17042
+ {
17043
+ const { riskName } = backtest$1.strategySchemaService.get(strategyName);
17044
+ riskName && backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
17045
+ }
17046
+ await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
17047
+ };
17048
+ }
17049
+ }
17050
+ /**
17051
+ * Global singleton instance of RiskUtils.
17052
+ * Provides static-like access to risk rejection reporting methods.
17053
+ *
17054
+ * @example
17055
+ * ```typescript
17056
+ * import { Risk } from "backtest-kit";
17057
+ *
17058
+ * // Usage same as RiskUtils methods
17059
+ * const stats = await Risk.getData("BTCUSDT", "my-strategy");
17060
+ * const report = await Risk.getReport("BTCUSDT", "my-strategy");
17061
+ * await Risk.dump("BTCUSDT", "my-strategy");
17062
+ * ```
17063
+ */
17064
+ const Risk = new RiskUtils();
17065
+
17066
+ 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 };