backtest-kit 1.5.15 → 1.5.17

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
@@ -200,6 +200,7 @@ const markdownServices$1 = {
200
200
  heatMarkdownService: Symbol('heatMarkdownService'),
201
201
  partialMarkdownService: Symbol('partialMarkdownService'),
202
202
  outlineMarkdownService: Symbol('outlineMarkdownService'),
203
+ riskMarkdownService: Symbol('riskMarkdownService'),
203
204
  };
204
205
  const validationServices$1 = {
205
206
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -440,11 +441,12 @@ const GET_CANDLES_FN = async (dto, since, self) => {
440
441
  }
441
442
  catch (err) {
442
443
  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, {
444
+ const payload = {
444
445
  error: errorData(err),
445
446
  message: getErrorMessage(err),
446
- });
447
- console.warn(message);
447
+ };
448
+ self.params.logger.warn(message, payload);
449
+ console.warn(message, payload);
448
450
  lastError = err;
449
451
  await sleep(GLOBAL_CONFIG.CC_GET_CANDLES_RETRY_DELAY_MS);
450
452
  }
@@ -1723,6 +1725,12 @@ const partialProfitSubject = new Subject();
1723
1725
  * Emits when a signal reaches a loss level (10%, 20%, 30%, etc).
1724
1726
  */
1725
1727
  const partialLossSubject = new Subject();
1728
+ /**
1729
+ * Risk rejection emitter for risk management violations.
1730
+ * Emits ONLY when a signal is rejected due to risk validation failure.
1731
+ * Does not emit for allowed signals (prevents spam).
1732
+ */
1733
+ const riskSubject = new Subject();
1726
1734
 
1727
1735
  var emitters = /*#__PURE__*/Object.freeze({
1728
1736
  __proto__: null,
@@ -1737,6 +1745,7 @@ var emitters = /*#__PURE__*/Object.freeze({
1737
1745
  progressBacktestEmitter: progressBacktestEmitter,
1738
1746
  progressOptimizerEmitter: progressOptimizerEmitter,
1739
1747
  progressWalkerEmitter: progressWalkerEmitter,
1748
+ riskSubject: riskSubject,
1740
1749
  signalBacktestEmitter: signalBacktestEmitter,
1741
1750
  signalEmitter: signalEmitter,
1742
1751
  signalLiveEmitter: signalLiveEmitter,
@@ -1823,6 +1832,9 @@ const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
1823
1832
  if (signal.position === undefined || signal.position === null) {
1824
1833
  errors.push('position is required and must be "long" or "short"');
1825
1834
  }
1835
+ if (signal.position !== "long" && signal.position !== "short") {
1836
+ errors.push(`position must be "long" or "short", got "${signal.position}"`);
1837
+ }
1826
1838
  // ЗАЩИТА ОТ NaN/Infinity: currentPrice должна быть конечным числом
1827
1839
  if (!isFinite(currentPrice)) {
1828
1840
  errors.push(`currentPrice must be a finite number, got ${currentPrice} (${typeof currentPrice})`);
@@ -2109,10 +2121,13 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
2109
2121
  }, {
2110
2122
  defaultValue: null,
2111
2123
  fallback: (error) => {
2112
- backtest$1.loggerService.warn("ClientStrategy exception thrown", {
2124
+ const message = "ClientStrategy exception thrown";
2125
+ const payload = {
2113
2126
  error: errorData(error),
2114
2127
  message: getErrorMessage(error),
2115
- });
2128
+ };
2129
+ backtest$1.loggerService.warn(message, payload);
2130
+ console.warn(message, payload);
2116
2131
  errorEmitter.next(error);
2117
2132
  },
2118
2133
  });
@@ -2803,7 +2818,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2803
2818
  // Moving towards TP
2804
2819
  const tpDistance = signal.priceTakeProfit - signal.priceOpen;
2805
2820
  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);
2821
+ 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
2822
  if (self.params.callbacks?.onPartialProfit) {
2808
2823
  self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2809
2824
  }
@@ -2812,7 +2827,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2812
2827
  // Moving towards SL
2813
2828
  const slDistance = signal.priceOpen - signal.priceStopLoss;
2814
2829
  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);
2830
+ 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
2831
  if (self.params.callbacks?.onPartialLoss) {
2817
2832
  self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2818
2833
  }
@@ -2825,7 +2840,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2825
2840
  // Moving towards TP
2826
2841
  const tpDistance = signal.priceOpen - signal.priceTakeProfit;
2827
2842
  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);
2843
+ 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
2844
  if (self.params.callbacks?.onPartialProfit) {
2830
2845
  self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2831
2846
  }
@@ -2834,7 +2849,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2834
2849
  // Moving towards SL
2835
2850
  const slDistance = signal.priceStopLoss - signal.priceOpen;
2836
2851
  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);
2852
+ 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
2853
  if (self.params.callbacks?.onPartialLoss) {
2839
2854
  self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2840
2855
  }
@@ -3789,10 +3804,13 @@ const DO_VALIDATION_FN = trycatch(async (validation, params) => {
3789
3804
  }, {
3790
3805
  defaultValue: false,
3791
3806
  fallback: (error) => {
3792
- backtest$1.loggerService.warn("ClientRisk exception thrown", {
3807
+ const message = "ClientRisk exception thrown";
3808
+ const payload = {
3793
3809
  error: errorData(error),
3794
3810
  message: getErrorMessage(error),
3795
- });
3811
+ };
3812
+ backtest$1.loggerService.warn(message, payload);
3813
+ console.warn(message, payload);
3796
3814
  validationSubject.next(error);
3797
3815
  },
3798
3816
  });
@@ -3862,17 +3880,25 @@ class ClientRisk {
3862
3880
  };
3863
3881
  // Execute custom validations
3864
3882
  let isValid = true;
3883
+ let rejectionNote = "N/A";
3865
3884
  if (this.params.validations) {
3866
3885
  for (const validation of this.params.validations) {
3867
3886
  if (not(await DO_VALIDATION_FN(typeof validation === "function"
3868
3887
  ? validation
3869
3888
  : validation.validate, payload))) {
3870
3889
  isValid = false;
3890
+ // Capture note from validation if available
3891
+ if (typeof validation !== "function" && validation.note) {
3892
+ rejectionNote = validation.note;
3893
+ }
3871
3894
  break;
3872
3895
  }
3873
3896
  }
3874
3897
  }
3875
3898
  if (!isValid) {
3899
+ // Call params.onRejected for riskSubject emission
3900
+ await this.params.onRejected(params.symbol, params, riskMap.size, rejectionNote, Date.now());
3901
+ // Call schema callbacks.onRejected if defined
3876
3902
  if (this.params.callbacks?.onRejected) {
3877
3903
  this.params.callbacks.onRejected(params.symbol, params);
3878
3904
  }
@@ -3935,6 +3961,28 @@ class ClientRisk {
3935
3961
  }
3936
3962
  }
3937
3963
 
3964
+ /**
3965
+ * Callback function for emitting risk rejection events to riskSubject.
3966
+ *
3967
+ * Called by ClientRisk when a signal is rejected due to risk validation failure.
3968
+ * Emits RiskContract event to all subscribers.
3969
+ *
3970
+ * @param symbol - Trading pair symbol
3971
+ * @param params - Risk check arguments
3972
+ * @param activePositionCount - Number of active positions at rejection time
3973
+ * @param comment - Rejection reason from validation note or "N/A"
3974
+ * @param timestamp - Event timestamp in milliseconds
3975
+ */
3976
+ const COMMIT_REJECTION_FN = async (symbol, params, activePositionCount, comment, timestamp) => await riskSubject.next({
3977
+ symbol,
3978
+ pendingSignal: params.pendingSignal,
3979
+ strategyName: params.strategyName,
3980
+ exchangeName: params.exchangeName,
3981
+ currentPrice: params.currentPrice,
3982
+ activePositionCount,
3983
+ comment,
3984
+ timestamp,
3985
+ });
3938
3986
  /**
3939
3987
  * Connection service routing risk operations to correct ClientRisk instance.
3940
3988
  *
@@ -3985,6 +4033,7 @@ class RiskConnectionService {
3985
4033
  return new ClientRisk({
3986
4034
  ...schema,
3987
4035
  logger: this.loggerService,
4036
+ onRejected: COMMIT_REJECTION_FN,
3988
4037
  });
3989
4038
  });
3990
4039
  /**
@@ -3992,6 +4041,7 @@ class RiskConnectionService {
3992
4041
  *
3993
4042
  * Routes to appropriate ClientRisk instance based on provided context.
3994
4043
  * Validates portfolio drawdown, symbol exposure, position count, and daily loss limits.
4044
+ * ClientRisk will emit riskSubject event via onRejected callback when signal is rejected.
3995
4045
  *
3996
4046
  * @param params - Risk check arguments (portfolio state, position details)
3997
4047
  * @param context - Execution context with risk name
@@ -5980,7 +6030,7 @@ function isUnsafe$3(value) {
5980
6030
  }
5981
6031
  return false;
5982
6032
  }
5983
- const columns$4 = [
6033
+ const columns$5 = [
5984
6034
  {
5985
6035
  key: "signalId",
5986
6036
  label: "Signal ID",
@@ -6054,11 +6104,13 @@ const columns$4 = [
6054
6104
  format: (data) => new Date(data.closeTimestamp).toISOString(),
6055
6105
  },
6056
6106
  ];
6107
+ /** Maximum number of signals to store in backtest reports */
6108
+ const MAX_EVENTS$6 = 250;
6057
6109
  /**
6058
6110
  * Storage class for accumulating closed signals per strategy.
6059
6111
  * Maintains a list of all closed signals and provides methods to generate reports.
6060
6112
  */
6061
- let ReportStorage$4 = class ReportStorage {
6113
+ let ReportStorage$5 = class ReportStorage {
6062
6114
  constructor() {
6063
6115
  /** Internal list of all closed signals for this strategy */
6064
6116
  this._signalList = [];
@@ -6069,7 +6121,11 @@ let ReportStorage$4 = class ReportStorage {
6069
6121
  * @param data - Closed signal data with PNL and close reason
6070
6122
  */
6071
6123
  addSignal(data) {
6072
- this._signalList.push(data);
6124
+ this._signalList.unshift(data);
6125
+ // Trim queue if exceeded MAX_EVENTS
6126
+ if (this._signalList.length > MAX_EVENTS$6) {
6127
+ this._signalList.pop();
6128
+ }
6073
6129
  }
6074
6130
  /**
6075
6131
  * Calculates statistical data from closed signals (Controller).
@@ -6152,9 +6208,9 @@ let ReportStorage$4 = class ReportStorage {
6152
6208
  "No signals closed yet."
6153
6209
  ].join("\n");
6154
6210
  }
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)));
6211
+ const header = columns$5.map((col) => col.label);
6212
+ const separator = columns$5.map(() => "---");
6213
+ const rows = this._signalList.map((closedSignal) => columns$5.map((col) => col.format(closedSignal)));
6158
6214
  const tableData = [header, separator, ...rows];
6159
6215
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6160
6216
  return [
@@ -6230,7 +6286,7 @@ class BacktestMarkdownService {
6230
6286
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
6231
6287
  * Each symbol-strategy combination gets its own isolated storage instance.
6232
6288
  */
6233
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$4());
6289
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$5());
6234
6290
  /**
6235
6291
  * Processes tick events and accumulates closed signals.
6236
6292
  * Should be called from IStrategyCallbacks.onTick.
@@ -6401,7 +6457,7 @@ function isUnsafe$2(value) {
6401
6457
  }
6402
6458
  return false;
6403
6459
  }
6404
- const columns$3 = [
6460
+ const columns$4 = [
6405
6461
  {
6406
6462
  key: "timestamp",
6407
6463
  label: "Timestamp",
@@ -6485,12 +6541,12 @@ const columns$3 = [
6485
6541
  },
6486
6542
  ];
6487
6543
  /** Maximum number of events to store in live trading reports */
6488
- const MAX_EVENTS$3 = 250;
6544
+ const MAX_EVENTS$5 = 250;
6489
6545
  /**
6490
6546
  * Storage class for accumulating all tick events per strategy.
6491
6547
  * Maintains a chronological list of all events (idle, opened, active, closed).
6492
6548
  */
6493
- let ReportStorage$3 = class ReportStorage {
6549
+ let ReportStorage$4 = class ReportStorage {
6494
6550
  constructor() {
6495
6551
  /** Internal list of all tick events for this strategy */
6496
6552
  this._eventList = [];
@@ -6517,9 +6573,9 @@ let ReportStorage$3 = class ReportStorage {
6517
6573
  return;
6518
6574
  }
6519
6575
  {
6520
- this._eventList.push(newEvent);
6521
- if (this._eventList.length > MAX_EVENTS$3) {
6522
- this._eventList.shift();
6576
+ this._eventList.unshift(newEvent);
6577
+ if (this._eventList.length > MAX_EVENTS$5) {
6578
+ this._eventList.pop();
6523
6579
  }
6524
6580
  }
6525
6581
  }
@@ -6529,7 +6585,7 @@ let ReportStorage$3 = class ReportStorage {
6529
6585
  * @param data - Opened tick result
6530
6586
  */
6531
6587
  addOpenedEvent(data) {
6532
- this._eventList.push({
6588
+ this._eventList.unshift({
6533
6589
  timestamp: data.signal.pendingAt,
6534
6590
  action: "opened",
6535
6591
  symbol: data.signal.symbol,
@@ -6542,12 +6598,13 @@ let ReportStorage$3 = class ReportStorage {
6542
6598
  stopLoss: data.signal.priceStopLoss,
6543
6599
  });
6544
6600
  // Trim queue if exceeded MAX_EVENTS
6545
- if (this._eventList.length > MAX_EVENTS$3) {
6546
- this._eventList.shift();
6601
+ if (this._eventList.length > MAX_EVENTS$5) {
6602
+ this._eventList.pop();
6547
6603
  }
6548
6604
  }
6549
6605
  /**
6550
6606
  * Adds an active event to the storage.
6607
+ * Replaces the last active event with the same signalId.
6551
6608
  *
6552
6609
  * @param data - Active tick result
6553
6610
  */
@@ -6566,10 +6623,18 @@ let ReportStorage$3 = class ReportStorage {
6566
6623
  percentTp: data.percentTp,
6567
6624
  percentSl: data.percentSl,
6568
6625
  };
6569
- this._eventList.push(newEvent);
6626
+ // Find the last active event with the same signalId
6627
+ const lastActiveIndex = this._eventList.findLastIndex((event) => event.action === "active" && event.signalId === data.signal.id);
6628
+ // Replace the last active event with the same signalId
6629
+ if (lastActiveIndex !== -1) {
6630
+ this._eventList[lastActiveIndex] = newEvent;
6631
+ return;
6632
+ }
6633
+ // If no previous active event found, add new event
6634
+ this._eventList.unshift(newEvent);
6570
6635
  // Trim queue if exceeded MAX_EVENTS
6571
- if (this._eventList.length > MAX_EVENTS$3) {
6572
- this._eventList.shift();
6636
+ if (this._eventList.length > MAX_EVENTS$5) {
6637
+ this._eventList.pop();
6573
6638
  }
6574
6639
  }
6575
6640
  /**
@@ -6595,10 +6660,10 @@ let ReportStorage$3 = class ReportStorage {
6595
6660
  closeReason: data.closeReason,
6596
6661
  duration: durationMin,
6597
6662
  };
6598
- this._eventList.push(newEvent);
6663
+ this._eventList.unshift(newEvent);
6599
6664
  // Trim queue if exceeded MAX_EVENTS
6600
- if (this._eventList.length > MAX_EVENTS$3) {
6601
- this._eventList.shift();
6665
+ if (this._eventList.length > MAX_EVENTS$5) {
6666
+ this._eventList.pop();
6602
6667
  }
6603
6668
  }
6604
6669
  /**
@@ -6697,9 +6762,9 @@ let ReportStorage$3 = class ReportStorage {
6697
6762
  "No events recorded yet."
6698
6763
  ].join("\n");
6699
6764
  }
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)));
6765
+ const header = columns$4.map((col) => col.label);
6766
+ const separator = columns$4.map(() => "---");
6767
+ const rows = this._eventList.map((event) => columns$4.map((col) => col.format(event)));
6703
6768
  const tableData = [header, separator, ...rows];
6704
6769
  const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6705
6770
  return [
@@ -6778,7 +6843,7 @@ class LiveMarkdownService {
6778
6843
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
6779
6844
  * Each symbol-strategy combination gets its own isolated storage instance.
6780
6845
  */
6781
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$3());
6846
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$4());
6782
6847
  /**
6783
6848
  * Processes tick events and accumulates all event types.
6784
6849
  * Should be called from IStrategyCallbacks.onTick.
@@ -6941,7 +7006,7 @@ class LiveMarkdownService {
6941
7006
  }
6942
7007
  }
6943
7008
 
6944
- const columns$2 = [
7009
+ const columns$3 = [
6945
7010
  {
6946
7011
  key: "timestamp",
6947
7012
  label: "Timestamp",
@@ -6999,12 +7064,12 @@ const columns$2 = [
6999
7064
  },
7000
7065
  ];
7001
7066
  /** Maximum number of events to store in schedule reports */
7002
- const MAX_EVENTS$2 = 250;
7067
+ const MAX_EVENTS$4 = 250;
7003
7068
  /**
7004
7069
  * Storage class for accumulating scheduled signal events per strategy.
7005
7070
  * Maintains a chronological list of scheduled and cancelled events.
7006
7071
  */
7007
- let ReportStorage$2 = class ReportStorage {
7072
+ let ReportStorage$3 = class ReportStorage {
7008
7073
  constructor() {
7009
7074
  /** Internal list of all scheduled events for this strategy */
7010
7075
  this._eventList = [];
@@ -7015,7 +7080,7 @@ let ReportStorage$2 = class ReportStorage {
7015
7080
  * @param data - Scheduled tick result
7016
7081
  */
7017
7082
  addScheduledEvent(data) {
7018
- this._eventList.push({
7083
+ this._eventList.unshift({
7019
7084
  timestamp: data.signal.scheduledAt,
7020
7085
  action: "scheduled",
7021
7086
  symbol: data.signal.symbol,
@@ -7028,8 +7093,35 @@ let ReportStorage$2 = class ReportStorage {
7028
7093
  stopLoss: data.signal.priceStopLoss,
7029
7094
  });
7030
7095
  // Trim queue if exceeded MAX_EVENTS
7031
- if (this._eventList.length > MAX_EVENTS$2) {
7032
- this._eventList.shift();
7096
+ if (this._eventList.length > MAX_EVENTS$4) {
7097
+ this._eventList.pop();
7098
+ }
7099
+ }
7100
+ /**
7101
+ * Adds an opened event to the storage.
7102
+ *
7103
+ * @param data - Opened tick result
7104
+ */
7105
+ addOpenedEvent(data) {
7106
+ const durationMs = data.signal.pendingAt - data.signal.scheduledAt;
7107
+ const durationMin = Math.round(durationMs / 60000);
7108
+ const newEvent = {
7109
+ timestamp: data.signal.pendingAt,
7110
+ action: "opened",
7111
+ symbol: data.signal.symbol,
7112
+ signalId: data.signal.id,
7113
+ position: data.signal.position,
7114
+ note: data.signal.note,
7115
+ currentPrice: data.currentPrice,
7116
+ priceOpen: data.signal.priceOpen,
7117
+ takeProfit: data.signal.priceTakeProfit,
7118
+ stopLoss: data.signal.priceStopLoss,
7119
+ duration: durationMin,
7120
+ };
7121
+ this._eventList.unshift(newEvent);
7122
+ // Trim queue if exceeded MAX_EVENTS
7123
+ if (this._eventList.length > MAX_EVENTS$4) {
7124
+ this._eventList.pop();
7033
7125
  }
7034
7126
  }
7035
7127
  /**
@@ -7054,10 +7146,10 @@ let ReportStorage$2 = class ReportStorage {
7054
7146
  closeTimestamp: data.closeTimestamp,
7055
7147
  duration: durationMin,
7056
7148
  };
7057
- this._eventList.push(newEvent);
7149
+ this._eventList.unshift(newEvent);
7058
7150
  // Trim queue if exceeded MAX_EVENTS
7059
- if (this._eventList.length > MAX_EVENTS$2) {
7060
- this._eventList.shift();
7151
+ if (this._eventList.length > MAX_EVENTS$4) {
7152
+ this._eventList.pop();
7061
7153
  }
7062
7154
  }
7063
7155
  /**
@@ -7071,29 +7163,44 @@ let ReportStorage$2 = class ReportStorage {
7071
7163
  eventList: [],
7072
7164
  totalEvents: 0,
7073
7165
  totalScheduled: 0,
7166
+ totalOpened: 0,
7074
7167
  totalCancelled: 0,
7075
7168
  cancellationRate: null,
7169
+ activationRate: null,
7076
7170
  avgWaitTime: null,
7171
+ avgActivationTime: null,
7077
7172
  };
7078
7173
  }
7079
7174
  const scheduledEvents = this._eventList.filter((e) => e.action === "scheduled");
7175
+ const openedEvents = this._eventList.filter((e) => e.action === "opened");
7080
7176
  const cancelledEvents = this._eventList.filter((e) => e.action === "cancelled");
7081
7177
  const totalScheduled = scheduledEvents.length;
7178
+ const totalOpened = openedEvents.length;
7082
7179
  const totalCancelled = cancelledEvents.length;
7083
7180
  // Calculate cancellation rate
7084
7181
  const cancellationRate = totalScheduled > 0 ? (totalCancelled / totalScheduled) * 100 : null;
7182
+ // Calculate activation rate
7183
+ const activationRate = totalScheduled > 0 ? (totalOpened / totalScheduled) * 100 : null;
7085
7184
  // Calculate average wait time for cancelled signals
7086
7185
  const avgWaitTime = totalCancelled > 0
7087
7186
  ? cancelledEvents.reduce((sum, e) => sum + (e.duration || 0), 0) /
7088
7187
  totalCancelled
7089
7188
  : null;
7189
+ // Calculate average activation time for opened signals
7190
+ const avgActivationTime = totalOpened > 0
7191
+ ? openedEvents.reduce((sum, e) => sum + (e.duration || 0), 0) /
7192
+ totalOpened
7193
+ : null;
7090
7194
  return {
7091
7195
  eventList: this._eventList,
7092
7196
  totalEvents: this._eventList.length,
7093
7197
  totalScheduled,
7198
+ totalOpened,
7094
7199
  totalCancelled,
7095
7200
  cancellationRate,
7201
+ activationRate,
7096
7202
  avgWaitTime,
7203
+ avgActivationTime,
7097
7204
  };
7098
7205
  }
7099
7206
  /**
@@ -7111,9 +7218,9 @@ let ReportStorage$2 = class ReportStorage {
7111
7218
  "No scheduled signals recorded yet."
7112
7219
  ].join("\n");
7113
7220
  }
7114
- const header = columns$2.map((col) => col.label);
7115
- const separator = columns$2.map(() => "---");
7116
- const rows = this._eventList.map((event) => columns$2.map((col) => col.format(event)));
7221
+ const header = columns$3.map((col) => col.label);
7222
+ const separator = columns$3.map(() => "---");
7223
+ const rows = this._eventList.map((event) => columns$3.map((col) => col.format(event)));
7117
7224
  const tableData = [header, separator, ...rows];
7118
7225
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7119
7226
  return [
@@ -7123,8 +7230,11 @@ let ReportStorage$2 = class ReportStorage {
7123
7230
  "",
7124
7231
  `**Total events:** ${stats.totalEvents}`,
7125
7232
  `**Scheduled signals:** ${stats.totalScheduled}`,
7233
+ `**Opened signals:** ${stats.totalOpened}`,
7126
7234
  `**Cancelled signals:** ${stats.totalCancelled}`,
7235
+ `**Activation rate:** ${stats.activationRate === null ? "N/A" : `${stats.activationRate.toFixed(2)}% (higher is better)`}`,
7127
7236
  `**Cancellation rate:** ${stats.cancellationRate === null ? "N/A" : `${stats.cancellationRate.toFixed(2)}% (lower is better)`}`,
7237
+ `**Average activation time:** ${stats.avgActivationTime === null ? "N/A" : `${stats.avgActivationTime.toFixed(2)} minutes`}`,
7128
7238
  `**Average wait time (cancelled):** ${stats.avgWaitTime === null ? "N/A" : `${stats.avgWaitTime.toFixed(2)} minutes`}`
7129
7239
  ].join("\n");
7130
7240
  }
@@ -7178,12 +7288,12 @@ class ScheduleMarkdownService {
7178
7288
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
7179
7289
  * Each symbol-strategy combination gets its own isolated storage instance.
7180
7290
  */
7181
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$2());
7291
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$3());
7182
7292
  /**
7183
- * Processes tick events and accumulates scheduled/cancelled events.
7184
- * Should be called from signalLiveEmitter subscription.
7293
+ * Processes tick events and accumulates scheduled/opened/cancelled events.
7294
+ * Should be called from signalEmitter subscription.
7185
7295
  *
7186
- * Processes only scheduled and cancelled event types.
7296
+ * Processes only scheduled, opened and cancelled event types.
7187
7297
  *
7188
7298
  * @param data - Tick result from strategy execution
7189
7299
  *
@@ -7201,6 +7311,13 @@ class ScheduleMarkdownService {
7201
7311
  if (data.action === "scheduled") {
7202
7312
  storage.addScheduledEvent(data);
7203
7313
  }
7314
+ else if (data.action === "opened") {
7315
+ // Check if this opened signal was previously scheduled
7316
+ // by checking if signal has scheduledAt != pendingAt
7317
+ if (data.signal.scheduledAt !== data.signal.pendingAt) {
7318
+ storage.addOpenedEvent(data);
7319
+ }
7320
+ }
7204
7321
  else if (data.action === "cancelled") {
7205
7322
  storage.addCancelledEvent(data);
7206
7323
  }
@@ -7338,7 +7455,7 @@ function percentile(sortedArray, p) {
7338
7455
  return sortedArray[Math.max(0, index)];
7339
7456
  }
7340
7457
  /** Maximum number of performance events to store per strategy */
7341
- const MAX_EVENTS$1 = 10000;
7458
+ const MAX_EVENTS$3 = 10000;
7342
7459
  /**
7343
7460
  * Storage class for accumulating performance metrics per strategy.
7344
7461
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -7354,10 +7471,10 @@ class PerformanceStorage {
7354
7471
  * @param event - Performance event with timing data
7355
7472
  */
7356
7473
  addEvent(event) {
7357
- this._events.push(event);
7474
+ this._events.unshift(event);
7358
7475
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
7359
- if (this._events.length > MAX_EVENTS$1) {
7360
- this._events.shift();
7476
+ if (this._events.length > MAX_EVENTS$3) {
7477
+ this._events.pop();
7361
7478
  }
7362
7479
  }
7363
7480
  /**
@@ -7823,7 +7940,7 @@ const pnlColumns = [
7823
7940
  * Storage class for accumulating walker results.
7824
7941
  * Maintains a list of all strategy results and provides methods to generate reports.
7825
7942
  */
7826
- let ReportStorage$1 = class ReportStorage {
7943
+ let ReportStorage$2 = class ReportStorage {
7827
7944
  constructor(walkerName) {
7828
7945
  this.walkerName = walkerName;
7829
7946
  /** Walker metadata (set from first addResult call) */
@@ -7841,17 +7958,13 @@ let ReportStorage$1 = class ReportStorage {
7841
7958
  * @param data - Walker contract with strategy result
7842
7959
  */
7843
7960
  addResult(data) {
7844
- {
7845
- this._bestMetric = data.bestMetric;
7846
- this._bestStrategy = data.bestStrategy;
7847
- this._totalStrategies = data.totalStrategies;
7848
- }
7849
- // Update best stats only if this strategy is the current best
7961
+ this._totalStrategies = data.totalStrategies;
7962
+ this._bestMetric = data.bestMetric;
7963
+ this._bestStrategy = data.bestStrategy;
7850
7964
  if (data.strategyName === data.bestStrategy) {
7851
7965
  this._bestStats = data.stats;
7852
7966
  }
7853
- // Add strategy result to comparison list
7854
- this._strategyResults.push({
7967
+ this._strategyResults.unshift({
7855
7968
  strategyName: data.strategyName,
7856
7969
  stats: data.stats,
7857
7970
  metricValue: data.metricValue,
@@ -8035,7 +8148,7 @@ class WalkerMarkdownService {
8035
8148
  * Memoized function to get or create ReportStorage for a walker.
8036
8149
  * Each walker gets its own isolated storage instance.
8037
8150
  */
8038
- this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$1(walkerName));
8151
+ this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$2(walkerName));
8039
8152
  /**
8040
8153
  * Processes walker progress events and accumulates strategy results.
8041
8154
  * Should be called from walkerEmitter.
@@ -8206,7 +8319,7 @@ function isUnsafe(value) {
8206
8319
  }
8207
8320
  return false;
8208
8321
  }
8209
- const columns$1 = [
8322
+ const columns$2 = [
8210
8323
  {
8211
8324
  key: "symbol",
8212
8325
  label: "Symbol",
@@ -8268,6 +8381,8 @@ const columns$1 = [
8268
8381
  format: (data) => data.totalTrades.toString(),
8269
8382
  },
8270
8383
  ];
8384
+ /** Maximum number of signals to store per symbol in heatmap reports */
8385
+ const MAX_EVENTS$2 = 250;
8271
8386
  /**
8272
8387
  * Storage class for accumulating closed signals per strategy and generating heatmap.
8273
8388
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -8287,7 +8402,12 @@ class HeatmapStorage {
8287
8402
  if (!this.symbolData.has(symbol)) {
8288
8403
  this.symbolData.set(symbol, []);
8289
8404
  }
8290
- this.symbolData.get(symbol).push(data);
8405
+ const signals = this.symbolData.get(symbol);
8406
+ signals.unshift(data);
8407
+ // Trim queue if exceeded MAX_EVENTS per symbol
8408
+ if (signals.length > MAX_EVENTS$2) {
8409
+ signals.pop();
8410
+ }
8291
8411
  }
8292
8412
  /**
8293
8413
  * Calculates statistics for a single symbol.
@@ -8506,9 +8626,9 @@ class HeatmapStorage {
8506
8626
  "*No data available*"
8507
8627
  ].join("\n");
8508
8628
  }
8509
- const header = columns$1.map((col) => col.label);
8510
- const separator = columns$1.map(() => "---");
8511
- const rows = data.symbols.map((row) => columns$1.map((col) => col.format(row)));
8629
+ const header = columns$2.map((col) => col.label);
8630
+ const separator = columns$2.map(() => "---");
8631
+ const rows = data.symbols.map((row) => columns$2.map((col) => col.format(row)));
8512
8632
  const tableData = [header, separator, ...rows];
8513
8633
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8514
8634
  return [
@@ -10546,7 +10666,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
10546
10666
  revenuePercent,
10547
10667
  backtest,
10548
10668
  });
10549
- await self.params.onProfit(symbol, data, currentPrice, level, backtest, when.getTime());
10669
+ await self.params.onProfit(symbol, data.strategyName, data.exchangeName, data, currentPrice, level, backtest, when.getTime());
10550
10670
  }
10551
10671
  }
10552
10672
  if (shouldPersist) {
@@ -10593,7 +10713,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
10593
10713
  lossPercent,
10594
10714
  backtest,
10595
10715
  });
10596
- await self.params.onLoss(symbol, data, currentPrice, level, backtest, when.getTime());
10716
+ await self.params.onLoss(symbol, data.strategyName, data.exchangeName, data, currentPrice, level, backtest, when.getTime());
10597
10717
  }
10598
10718
  }
10599
10719
  if (shouldPersist) {
@@ -10864,6 +10984,7 @@ class ClientPartial {
10864
10984
  symbol,
10865
10985
  data,
10866
10986
  priceClose,
10987
+ backtest,
10867
10988
  });
10868
10989
  if (this._states === NEED_FETCH) {
10869
10990
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
@@ -10880,14 +11001,18 @@ class ClientPartial {
10880
11001
  * Emits PartialProfitContract event to all subscribers.
10881
11002
  *
10882
11003
  * @param symbol - Trading pair symbol
11004
+ * @param strategyName - Strategy name that generated this signal
11005
+ * @param exchangeName - Exchange name where this signal is being executed
10883
11006
  * @param data - Signal row data
10884
11007
  * @param currentPrice - Current market price
10885
11008
  * @param level - Profit level reached
10886
11009
  * @param backtest - True if backtest mode
10887
11010
  * @param timestamp - Event timestamp in milliseconds
10888
11011
  */
10889
- const COMMIT_PROFIT_FN = async (symbol, data, currentPrice, level, backtest, timestamp) => await partialProfitSubject.next({
11012
+ const COMMIT_PROFIT_FN = async (symbol, strategyName, exchangeName, data, currentPrice, level, backtest, timestamp) => await partialProfitSubject.next({
10890
11013
  symbol,
11014
+ strategyName,
11015
+ exchangeName,
10891
11016
  data,
10892
11017
  currentPrice,
10893
11018
  level,
@@ -10901,14 +11026,18 @@ const COMMIT_PROFIT_FN = async (symbol, data, currentPrice, level, backtest, tim
10901
11026
  * Emits PartialLossContract event to all subscribers.
10902
11027
  *
10903
11028
  * @param symbol - Trading pair symbol
11029
+ * @param strategyName - Strategy name that generated this signal
11030
+ * @param exchangeName - Exchange name where this signal is being executed
10904
11031
  * @param data - Signal row data
10905
11032
  * @param currentPrice - Current market price
10906
11033
  * @param level - Loss level reached
10907
11034
  * @param backtest - True if backtest mode
10908
11035
  * @param timestamp - Event timestamp in milliseconds
10909
11036
  */
10910
- const COMMIT_LOSS_FN = async (symbol, data, currentPrice, level, backtest, timestamp) => await partialLossSubject.next({
11037
+ const COMMIT_LOSS_FN = async (symbol, strategyName, exchangeName, data, currentPrice, level, backtest, timestamp) => await partialLossSubject.next({
10911
11038
  symbol,
11039
+ strategyName,
11040
+ exchangeName,
10912
11041
  data,
10913
11042
  currentPrice,
10914
11043
  level,
@@ -11054,7 +11183,7 @@ class PartialConnectionService {
11054
11183
  }
11055
11184
  }
11056
11185
 
11057
- const columns = [
11186
+ const columns$1 = [
11058
11187
  {
11059
11188
  key: "action",
11060
11189
  label: "Action",
@@ -11102,12 +11231,12 @@ const columns = [
11102
11231
  },
11103
11232
  ];
11104
11233
  /** Maximum number of events to store in partial reports */
11105
- const MAX_EVENTS = 250;
11234
+ const MAX_EVENTS$1 = 250;
11106
11235
  /**
11107
11236
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
11108
11237
  * Maintains a chronological list of profit and loss level events.
11109
11238
  */
11110
- class ReportStorage {
11239
+ let ReportStorage$1 = class ReportStorage {
11111
11240
  constructor() {
11112
11241
  /** Internal list of all partial events for this symbol */
11113
11242
  this._eventList = [];
@@ -11121,7 +11250,7 @@ class ReportStorage {
11121
11250
  * @param backtest - True if backtest mode
11122
11251
  */
11123
11252
  addProfitEvent(data, currentPrice, level, backtest, timestamp) {
11124
- this._eventList.push({
11253
+ this._eventList.unshift({
11125
11254
  timestamp,
11126
11255
  action: "profit",
11127
11256
  symbol: data.symbol,
@@ -11133,8 +11262,8 @@ class ReportStorage {
11133
11262
  backtest,
11134
11263
  });
11135
11264
  // Trim queue if exceeded MAX_EVENTS
11136
- if (this._eventList.length > MAX_EVENTS) {
11137
- this._eventList.shift();
11265
+ if (this._eventList.length > MAX_EVENTS$1) {
11266
+ this._eventList.pop();
11138
11267
  }
11139
11268
  }
11140
11269
  /**
@@ -11146,7 +11275,7 @@ class ReportStorage {
11146
11275
  * @param backtest - True if backtest mode
11147
11276
  */
11148
11277
  addLossEvent(data, currentPrice, level, backtest, timestamp) {
11149
- this._eventList.push({
11278
+ this._eventList.unshift({
11150
11279
  timestamp,
11151
11280
  action: "loss",
11152
11281
  symbol: data.symbol,
@@ -11158,8 +11287,8 @@ class ReportStorage {
11158
11287
  backtest,
11159
11288
  });
11160
11289
  // Trim queue if exceeded MAX_EVENTS
11161
- if (this._eventList.length > MAX_EVENTS) {
11162
- this._eventList.shift();
11290
+ if (this._eventList.length > MAX_EVENTS$1) {
11291
+ this._eventList.pop();
11163
11292
  }
11164
11293
  }
11165
11294
  /**
@@ -11201,9 +11330,9 @@ class ReportStorage {
11201
11330
  "No partial profit/loss events recorded yet."
11202
11331
  ].join("\n");
11203
11332
  }
11204
- const header = columns.map((col) => col.label);
11205
- const separator = columns.map(() => "---");
11206
- const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
11333
+ const header = columns$1.map((col) => col.label);
11334
+ const separator = columns$1.map(() => "---");
11335
+ const rows = this._eventList.map((event) => columns$1.map((col) => col.format(event)));
11207
11336
  const tableData = [header, separator, ...rows];
11208
11337
  const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
11209
11338
  return [
@@ -11237,7 +11366,7 @@ class ReportStorage {
11237
11366
  console.error(`Failed to save markdown report:`, error);
11238
11367
  }
11239
11368
  }
11240
- }
11369
+ };
11241
11370
  /**
11242
11371
  * Service for generating and saving partial profit/loss markdown reports.
11243
11372
  *
@@ -11267,7 +11396,7 @@ class PartialMarkdownService {
11267
11396
  * Memoized function to get or create ReportStorage for a symbol-strategy pair.
11268
11397
  * Each symbol-strategy combination gets its own isolated storage instance.
11269
11398
  */
11270
- this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage());
11399
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$1());
11271
11400
  /**
11272
11401
  * Processes profit events and accumulates them.
11273
11402
  * Should be called from partialProfitSubject subscription.
@@ -11577,7 +11706,7 @@ class PartialGlobalService {
11577
11706
  * Warning threshold for message size in kilobytes.
11578
11707
  * Messages exceeding this size trigger console warnings.
11579
11708
  */
11580
- const WARN_KB = 100;
11709
+ const WARN_KB = 20;
11581
11710
  /**
11582
11711
  * Internal function for dumping signal data to markdown files.
11583
11712
  * Creates a directory structure with system prompts, user messages, and LLM output.
@@ -11839,35 +11968,382 @@ class ConfigValidationService {
11839
11968
  }
11840
11969
  }
11841
11970
 
11842
- {
11843
- provide(TYPES.loggerService, () => new LoggerService());
11844
- }
11845
- {
11846
- provide(TYPES.executionContextService, () => new ExecutionContextService());
11847
- provide(TYPES.methodContextService, () => new MethodContextService());
11848
- }
11849
- {
11850
- provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
11851
- provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
11852
- provide(TYPES.frameConnectionService, () => new FrameConnectionService());
11853
- provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
11854
- provide(TYPES.riskConnectionService, () => new RiskConnectionService());
11855
- provide(TYPES.optimizerConnectionService, () => new OptimizerConnectionService());
11856
- provide(TYPES.partialConnectionService, () => new PartialConnectionService());
11857
- }
11858
- {
11859
- provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
11860
- provide(TYPES.strategySchemaService, () => new StrategySchemaService());
11861
- provide(TYPES.frameSchemaService, () => new FrameSchemaService());
11862
- provide(TYPES.walkerSchemaService, () => new WalkerSchemaService());
11863
- provide(TYPES.sizingSchemaService, () => new SizingSchemaService());
11864
- provide(TYPES.riskSchemaService, () => new RiskSchemaService());
11865
- provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
11971
+ const columns = [
11972
+ {
11973
+ key: "symbol",
11974
+ label: "Symbol",
11975
+ format: (data) => data.symbol,
11976
+ },
11977
+ {
11978
+ key: "strategyName",
11979
+ label: "Strategy",
11980
+ format: (data) => data.strategyName,
11981
+ },
11982
+ {
11983
+ key: "signalId",
11984
+ label: "Signal ID",
11985
+ format: (data) => data.pendingSignal.id || "N/A",
11986
+ },
11987
+ {
11988
+ key: "position",
11989
+ label: "Position",
11990
+ format: (data) => data.pendingSignal.position.toUpperCase(),
11991
+ },
11992
+ {
11993
+ key: "exchangeName",
11994
+ label: "Exchange",
11995
+ format: (data) => data.exchangeName,
11996
+ },
11997
+ {
11998
+ key: "openPrice",
11999
+ label: "Open Price",
12000
+ format: (data) => data.pendingSignal.priceOpen !== undefined
12001
+ ? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
12002
+ : "N/A",
12003
+ },
12004
+ {
12005
+ key: "takeProfit",
12006
+ label: "Take Profit",
12007
+ format: (data) => data.pendingSignal.priceTakeProfit !== undefined
12008
+ ? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
12009
+ : "N/A",
12010
+ },
12011
+ {
12012
+ key: "stopLoss",
12013
+ label: "Stop Loss",
12014
+ format: (data) => data.pendingSignal.priceStopLoss !== undefined
12015
+ ? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
12016
+ : "N/A",
12017
+ },
12018
+ {
12019
+ key: "currentPrice",
12020
+ label: "Current Price",
12021
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
12022
+ },
12023
+ {
12024
+ key: "activePositionCount",
12025
+ label: "Active Positions",
12026
+ format: (data) => data.activePositionCount.toString(),
12027
+ },
12028
+ {
12029
+ key: "comment",
12030
+ label: "Reason",
12031
+ format: (data) => data.comment,
12032
+ },
12033
+ {
12034
+ key: "timestamp",
12035
+ label: "Timestamp",
12036
+ format: (data) => new Date(data.timestamp).toISOString(),
12037
+ },
12038
+ ];
12039
+ /** Maximum number of events to store in risk reports */
12040
+ const MAX_EVENTS = 250;
12041
+ /**
12042
+ * Storage class for accumulating risk rejection events per symbol-strategy pair.
12043
+ * Maintains a chronological list of rejected signals due to risk limits.
12044
+ */
12045
+ class ReportStorage {
12046
+ constructor() {
12047
+ /** Internal list of all risk rejection events for this symbol */
12048
+ this._eventList = [];
12049
+ }
12050
+ /**
12051
+ * Adds a risk rejection event to the storage.
12052
+ *
12053
+ * @param event - Risk rejection event data
12054
+ */
12055
+ addRejectionEvent(event) {
12056
+ this._eventList.unshift(event);
12057
+ // Trim queue if exceeded MAX_EVENTS
12058
+ if (this._eventList.length > MAX_EVENTS) {
12059
+ this._eventList.pop();
12060
+ }
12061
+ }
12062
+ /**
12063
+ * Calculates statistical data from risk rejection events (Controller).
12064
+ *
12065
+ * @returns Statistical data (empty object if no events)
12066
+ */
12067
+ async getData() {
12068
+ if (this._eventList.length === 0) {
12069
+ return {
12070
+ eventList: [],
12071
+ totalRejections: 0,
12072
+ bySymbol: {},
12073
+ byStrategy: {},
12074
+ };
12075
+ }
12076
+ const bySymbol = {};
12077
+ const byStrategy = {};
12078
+ for (const event of this._eventList) {
12079
+ bySymbol[event.symbol] = (bySymbol[event.symbol] || 0) + 1;
12080
+ byStrategy[event.strategyName] = (byStrategy[event.strategyName] || 0) + 1;
12081
+ }
12082
+ return {
12083
+ eventList: this._eventList,
12084
+ totalRejections: this._eventList.length,
12085
+ bySymbol,
12086
+ byStrategy,
12087
+ };
12088
+ }
12089
+ /**
12090
+ * Generates markdown report with all risk rejection events for a symbol-strategy pair (View).
12091
+ *
12092
+ * @param symbol - Trading pair symbol
12093
+ * @param strategyName - Strategy name
12094
+ * @returns Markdown formatted report with all events
12095
+ */
12096
+ async getReport(symbol, strategyName) {
12097
+ const stats = await this.getData();
12098
+ if (stats.totalRejections === 0) {
12099
+ return [
12100
+ `# Risk Rejection Report: ${symbol}:${strategyName}`,
12101
+ "",
12102
+ "No risk rejections recorded yet.",
12103
+ ].join("\n");
12104
+ }
12105
+ const header = columns.map((col) => col.label);
12106
+ const separator = columns.map(() => "---");
12107
+ const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
12108
+ const tableData = [header, separator, ...rows];
12109
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
12110
+ return [
12111
+ `# Risk Rejection Report: ${symbol}:${strategyName}`,
12112
+ "",
12113
+ table,
12114
+ "",
12115
+ `**Total rejections:** ${stats.totalRejections}`,
12116
+ "",
12117
+ "## Rejections by Symbol",
12118
+ ...Object.entries(stats.bySymbol).map(([sym, count]) => `- ${sym}: ${count}`),
12119
+ "",
12120
+ "## Rejections by Strategy",
12121
+ ...Object.entries(stats.byStrategy).map(([strat, count]) => `- ${strat}: ${count}`),
12122
+ ].join("\n");
12123
+ }
12124
+ /**
12125
+ * Saves symbol-strategy report to disk.
12126
+ *
12127
+ * @param symbol - Trading pair symbol
12128
+ * @param strategyName - Strategy name
12129
+ * @param path - Directory path to save report (default: "./dump/risk")
12130
+ */
12131
+ async dump(symbol, strategyName, path = "./dump/risk") {
12132
+ const markdown = await this.getReport(symbol, strategyName);
12133
+ try {
12134
+ const dir = join(process.cwd(), path);
12135
+ await mkdir(dir, { recursive: true });
12136
+ const filename = `${symbol}_${strategyName}.md`;
12137
+ const filepath = join(dir, filename);
12138
+ await writeFile(filepath, markdown, "utf-8");
12139
+ console.log(`Risk rejection report saved: ${filepath}`);
12140
+ }
12141
+ catch (error) {
12142
+ console.error(`Failed to save markdown report:`, error);
12143
+ }
12144
+ }
11866
12145
  }
11867
- {
11868
- provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
11869
- provide(TYPES.strategyCoreService, () => new StrategyCoreService());
11870
- provide(TYPES.frameCoreService, () => new FrameCoreService());
12146
+ /**
12147
+ * Service for generating and saving risk rejection markdown reports.
12148
+ *
12149
+ * Features:
12150
+ * - Listens to risk rejection events via riskSubject
12151
+ * - Accumulates all rejection events per symbol-strategy pair
12152
+ * - Generates markdown tables with detailed rejection information
12153
+ * - Provides statistics (total rejections, by symbol, by strategy)
12154
+ * - Saves reports to disk in dump/risk/{symbol}_{strategyName}.md
12155
+ *
12156
+ * @example
12157
+ * ```typescript
12158
+ * const service = new RiskMarkdownService();
12159
+ *
12160
+ * // Service automatically subscribes to subjects on init
12161
+ * // No manual callback setup needed
12162
+ *
12163
+ * // Later: generate and save report
12164
+ * await service.dump("BTCUSDT", "my-strategy");
12165
+ * ```
12166
+ */
12167
+ class RiskMarkdownService {
12168
+ constructor() {
12169
+ /** Logger service for debug output */
12170
+ this.loggerService = inject(TYPES.loggerService);
12171
+ /**
12172
+ * Memoized function to get or create ReportStorage for a symbol-strategy pair.
12173
+ * Each symbol-strategy combination gets its own isolated storage instance.
12174
+ */
12175
+ this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage());
12176
+ /**
12177
+ * Processes risk rejection events and accumulates them.
12178
+ * Should be called from riskSubject subscription.
12179
+ *
12180
+ * @param data - Risk rejection event data
12181
+ *
12182
+ * @example
12183
+ * ```typescript
12184
+ * const service = new RiskMarkdownService();
12185
+ * // Service automatically subscribes in init()
12186
+ * ```
12187
+ */
12188
+ this.tickRejection = async (data) => {
12189
+ this.loggerService.log("riskMarkdownService tickRejection", {
12190
+ data,
12191
+ });
12192
+ const storage = this.getStorage(data.symbol, data.strategyName);
12193
+ storage.addRejectionEvent(data);
12194
+ };
12195
+ /**
12196
+ * Gets statistical data from all risk rejection events for a symbol-strategy pair.
12197
+ * Delegates to ReportStorage.getData().
12198
+ *
12199
+ * @param symbol - Trading pair symbol to get data for
12200
+ * @param strategyName - Strategy name to get data for
12201
+ * @returns Statistical data object with all metrics
12202
+ *
12203
+ * @example
12204
+ * ```typescript
12205
+ * const service = new RiskMarkdownService();
12206
+ * const stats = await service.getData("BTCUSDT", "my-strategy");
12207
+ * console.log(stats.totalRejections, stats.bySymbol);
12208
+ * ```
12209
+ */
12210
+ this.getData = async (symbol, strategyName) => {
12211
+ this.loggerService.log("riskMarkdownService getData", {
12212
+ symbol,
12213
+ strategyName,
12214
+ });
12215
+ const storage = this.getStorage(symbol, strategyName);
12216
+ return storage.getData();
12217
+ };
12218
+ /**
12219
+ * Generates markdown report with all risk rejection events for a symbol-strategy pair.
12220
+ * Delegates to ReportStorage.getReport().
12221
+ *
12222
+ * @param symbol - Trading pair symbol to generate report for
12223
+ * @param strategyName - Strategy name to generate report for
12224
+ * @returns Markdown formatted report string with table of all events
12225
+ *
12226
+ * @example
12227
+ * ```typescript
12228
+ * const service = new RiskMarkdownService();
12229
+ * const markdown = await service.getReport("BTCUSDT", "my-strategy");
12230
+ * console.log(markdown);
12231
+ * ```
12232
+ */
12233
+ this.getReport = async (symbol, strategyName) => {
12234
+ this.loggerService.log("riskMarkdownService getReport", {
12235
+ symbol,
12236
+ strategyName,
12237
+ });
12238
+ const storage = this.getStorage(symbol, strategyName);
12239
+ return storage.getReport(symbol, strategyName);
12240
+ };
12241
+ /**
12242
+ * Saves symbol-strategy report to disk.
12243
+ * Creates directory if it doesn't exist.
12244
+ * Delegates to ReportStorage.dump().
12245
+ *
12246
+ * @param symbol - Trading pair symbol to save report for
12247
+ * @param strategyName - Strategy name to save report for
12248
+ * @param path - Directory path to save report (default: "./dump/risk")
12249
+ *
12250
+ * @example
12251
+ * ```typescript
12252
+ * const service = new RiskMarkdownService();
12253
+ *
12254
+ * // Save to default path: ./dump/risk/BTCUSDT_my-strategy.md
12255
+ * await service.dump("BTCUSDT", "my-strategy");
12256
+ *
12257
+ * // Save to custom path: ./custom/path/BTCUSDT_my-strategy.md
12258
+ * await service.dump("BTCUSDT", "my-strategy", "./custom/path");
12259
+ * ```
12260
+ */
12261
+ this.dump = async (symbol, strategyName, path = "./dump/risk") => {
12262
+ this.loggerService.log("riskMarkdownService dump", {
12263
+ symbol,
12264
+ strategyName,
12265
+ path,
12266
+ });
12267
+ const storage = this.getStorage(symbol, strategyName);
12268
+ await storage.dump(symbol, strategyName, path);
12269
+ };
12270
+ /**
12271
+ * Clears accumulated event data from storage.
12272
+ * If ctx is provided, clears only that specific symbol-strategy pair's data.
12273
+ * If nothing is provided, clears all data.
12274
+ *
12275
+ * @param ctx - Optional context with symbol and strategyName
12276
+ *
12277
+ * @example
12278
+ * ```typescript
12279
+ * const service = new RiskMarkdownService();
12280
+ *
12281
+ * // Clear specific symbol-strategy pair
12282
+ * await service.clear({ symbol: "BTCUSDT", strategyName: "my-strategy" });
12283
+ *
12284
+ * // Clear all data
12285
+ * await service.clear();
12286
+ * ```
12287
+ */
12288
+ this.clear = async (ctx) => {
12289
+ this.loggerService.log("riskMarkdownService clear", {
12290
+ ctx,
12291
+ });
12292
+ if (ctx) {
12293
+ const key = `${ctx.symbol}:${ctx.strategyName}`;
12294
+ this.getStorage.clear(key);
12295
+ }
12296
+ else {
12297
+ this.getStorage.clear();
12298
+ }
12299
+ };
12300
+ /**
12301
+ * Initializes the service by subscribing to risk rejection events.
12302
+ * Uses singleshot to ensure initialization happens only once.
12303
+ * Automatically called on first use.
12304
+ *
12305
+ * @example
12306
+ * ```typescript
12307
+ * const service = new RiskMarkdownService();
12308
+ * await service.init(); // Subscribe to rejection events
12309
+ * ```
12310
+ */
12311
+ this.init = singleshot(async () => {
12312
+ this.loggerService.log("riskMarkdownService init");
12313
+ riskSubject.subscribe(this.tickRejection);
12314
+ });
12315
+ }
12316
+ }
12317
+
12318
+ {
12319
+ provide(TYPES.loggerService, () => new LoggerService());
12320
+ }
12321
+ {
12322
+ provide(TYPES.executionContextService, () => new ExecutionContextService());
12323
+ provide(TYPES.methodContextService, () => new MethodContextService());
12324
+ }
12325
+ {
12326
+ provide(TYPES.exchangeConnectionService, () => new ExchangeConnectionService());
12327
+ provide(TYPES.strategyConnectionService, () => new StrategyConnectionService());
12328
+ provide(TYPES.frameConnectionService, () => new FrameConnectionService());
12329
+ provide(TYPES.sizingConnectionService, () => new SizingConnectionService());
12330
+ provide(TYPES.riskConnectionService, () => new RiskConnectionService());
12331
+ provide(TYPES.optimizerConnectionService, () => new OptimizerConnectionService());
12332
+ provide(TYPES.partialConnectionService, () => new PartialConnectionService());
12333
+ }
12334
+ {
12335
+ provide(TYPES.exchangeSchemaService, () => new ExchangeSchemaService());
12336
+ provide(TYPES.strategySchemaService, () => new StrategySchemaService());
12337
+ provide(TYPES.frameSchemaService, () => new FrameSchemaService());
12338
+ provide(TYPES.walkerSchemaService, () => new WalkerSchemaService());
12339
+ provide(TYPES.sizingSchemaService, () => new SizingSchemaService());
12340
+ provide(TYPES.riskSchemaService, () => new RiskSchemaService());
12341
+ provide(TYPES.optimizerSchemaService, () => new OptimizerSchemaService());
12342
+ }
12343
+ {
12344
+ provide(TYPES.exchangeCoreService, () => new ExchangeCoreService());
12345
+ provide(TYPES.strategyCoreService, () => new StrategyCoreService());
12346
+ provide(TYPES.frameCoreService, () => new FrameCoreService());
11871
12347
  }
11872
12348
  {
11873
12349
  provide(TYPES.sizingGlobalService, () => new SizingGlobalService());
@@ -11899,6 +12375,7 @@ class ConfigValidationService {
11899
12375
  provide(TYPES.heatMarkdownService, () => new HeatMarkdownService());
11900
12376
  provide(TYPES.partialMarkdownService, () => new PartialMarkdownService());
11901
12377
  provide(TYPES.outlineMarkdownService, () => new OutlineMarkdownService());
12378
+ provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
11902
12379
  }
11903
12380
  {
11904
12381
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -11974,6 +12451,7 @@ const markdownServices = {
11974
12451
  heatMarkdownService: inject(TYPES.heatMarkdownService),
11975
12452
  partialMarkdownService: inject(TYPES.partialMarkdownService),
11976
12453
  outlineMarkdownService: inject(TYPES.outlineMarkdownService),
12454
+ riskMarkdownService: inject(TYPES.riskMarkdownService),
11977
12455
  };
11978
12456
  const validationServices = {
11979
12457
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -12748,6 +13226,8 @@ const LISTEN_PARTIAL_PROFIT_METHOD_NAME = "event.listenPartialProfit";
12748
13226
  const LISTEN_PARTIAL_PROFIT_ONCE_METHOD_NAME = "event.listenPartialProfitOnce";
12749
13227
  const LISTEN_PARTIAL_LOSS_METHOD_NAME = "event.listenPartialLoss";
12750
13228
  const LISTEN_PARTIAL_LOSS_ONCE_METHOD_NAME = "event.listenPartialLossOnce";
13229
+ const LISTEN_RISK_METHOD_NAME = "event.listenRisk";
13230
+ const LISTEN_RISK_ONCE_METHOD_NAME = "event.listenRiskOnce";
12751
13231
  /**
12752
13232
  * Subscribes to all signal events with queued async processing.
12753
13233
  *
@@ -13546,6 +14026,75 @@ function listenPartialLossOnce(filterFn, fn) {
13546
14026
  backtest$1.loggerService.log(LISTEN_PARTIAL_LOSS_ONCE_METHOD_NAME);
13547
14027
  return partialLossSubject.filter(filterFn).once(fn);
13548
14028
  }
14029
+ /**
14030
+ * Subscribes to risk rejection events with queued async processing.
14031
+ *
14032
+ * Emits ONLY when a signal is rejected due to risk validation failure.
14033
+ * Does not emit for allowed signals (prevents spam).
14034
+ * Events are processed sequentially in order received, even if callback is async.
14035
+ * Uses queued wrapper to prevent concurrent execution of the callback.
14036
+ *
14037
+ * @param fn - Callback function to handle risk rejection events
14038
+ * @returns Unsubscribe function to stop listening to events
14039
+ *
14040
+ * @example
14041
+ * ```typescript
14042
+ * import { listenRisk } from "./function/event";
14043
+ *
14044
+ * const unsubscribe = listenRisk((event) => {
14045
+ * console.log(`[RISK REJECTED] Signal for ${event.symbol}`);
14046
+ * console.log(`Strategy: ${event.strategyName}`);
14047
+ * console.log(`Position: ${event.pendingSignal.position}`);
14048
+ * console.log(`Active positions: ${event.activePositionCount}`);
14049
+ * console.log(`Reason: ${event.comment}`);
14050
+ * console.log(`Price: ${event.currentPrice}`);
14051
+ * });
14052
+ *
14053
+ * // Later: stop listening
14054
+ * unsubscribe();
14055
+ * ```
14056
+ */
14057
+ function listenRisk(fn) {
14058
+ backtest$1.loggerService.log(LISTEN_RISK_METHOD_NAME);
14059
+ return riskSubject.subscribe(queued(async (event) => fn(event)));
14060
+ }
14061
+ /**
14062
+ * Subscribes to filtered risk rejection events with one-time execution.
14063
+ *
14064
+ * Listens for events matching the filter predicate, then executes callback once
14065
+ * and automatically unsubscribes. Useful for waiting for specific risk rejection conditions.
14066
+ *
14067
+ * @param filterFn - Predicate to filter which events trigger the callback
14068
+ * @param fn - Callback function to handle the filtered event (called only once)
14069
+ * @returns Unsubscribe function to cancel the listener before it fires
14070
+ *
14071
+ * @example
14072
+ * ```typescript
14073
+ * import { listenRiskOnce } from "./function/event";
14074
+ *
14075
+ * // Wait for first risk rejection on BTCUSDT
14076
+ * listenRiskOnce(
14077
+ * (event) => event.symbol === "BTCUSDT",
14078
+ * (event) => {
14079
+ * console.log("BTCUSDT signal rejected!");
14080
+ * console.log("Reason:", event.comment);
14081
+ * }
14082
+ * );
14083
+ *
14084
+ * // Wait for rejection due to position limit
14085
+ * const cancel = listenRiskOnce(
14086
+ * (event) => event.comment.includes("Max") && event.activePositionCount >= 3,
14087
+ * (event) => console.log("Position limit reached:", event.activePositionCount)
14088
+ * );
14089
+ *
14090
+ * // Cancel if needed before event fires
14091
+ * cancel();
14092
+ * ```
14093
+ */
14094
+ function listenRiskOnce(filterFn, fn) {
14095
+ backtest$1.loggerService.log(LISTEN_RISK_ONCE_METHOD_NAME);
14096
+ return riskSubject.filter(filterFn).once(fn);
14097
+ }
13549
14098
 
13550
14099
  const GET_CANDLES_METHOD_NAME = "exchange.getCandles";
13551
14100
  const GET_AVERAGE_PRICE_METHOD_NAME = "exchange.getAveragePrice";
@@ -16170,4 +16719,184 @@ class ConstantUtils {
16170
16719
  */
16171
16720
  const Constant = new ConstantUtils();
16172
16721
 
16173
- 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 };
16722
+ const RISK_METHOD_NAME_GET_DATA = "RiskUtils.getData";
16723
+ const RISK_METHOD_NAME_GET_REPORT = "RiskUtils.getReport";
16724
+ const RISK_METHOD_NAME_DUMP = "RiskUtils.dump";
16725
+ /**
16726
+ * Utility class for accessing risk rejection reports and statistics.
16727
+ *
16728
+ * Provides static-like methods (via singleton instance) to retrieve data
16729
+ * accumulated by RiskMarkdownService from risk rejection events.
16730
+ *
16731
+ * Features:
16732
+ * - Statistical data extraction (total rejections count, by symbol, by strategy)
16733
+ * - Markdown report generation with event tables
16734
+ * - File export to disk
16735
+ *
16736
+ * Data source:
16737
+ * - RiskMarkdownService listens to riskSubject
16738
+ * - Accumulates rejection events in ReportStorage (max 250 events per symbol-strategy pair)
16739
+ * - Events include: timestamp, symbol, strategyName, position, exchangeName, price, activePositionCount, comment
16740
+ *
16741
+ * @example
16742
+ * ```typescript
16743
+ * import { Risk } from "./classes/Risk";
16744
+ *
16745
+ * // Get statistical data for BTCUSDT:my-strategy
16746
+ * const stats = await Risk.getData("BTCUSDT", "my-strategy");
16747
+ * console.log(`Total rejections: ${stats.totalRejections}`);
16748
+ * console.log(`By symbol:`, stats.bySymbol);
16749
+ * console.log(`By strategy:`, stats.byStrategy);
16750
+ *
16751
+ * // Generate markdown report
16752
+ * const markdown = await Risk.getReport("BTCUSDT", "my-strategy");
16753
+ * console.log(markdown); // Formatted table with all rejection events
16754
+ *
16755
+ * // Export report to file
16756
+ * await Risk.dump("BTCUSDT", "my-strategy"); // Saves to ./dump/risk/BTCUSDT_my-strategy.md
16757
+ * await Risk.dump("BTCUSDT", "my-strategy", "./custom/path"); // Custom directory
16758
+ * ```
16759
+ */
16760
+ class RiskUtils {
16761
+ constructor() {
16762
+ /**
16763
+ * Retrieves statistical data from accumulated risk rejection events.
16764
+ *
16765
+ * Delegates to RiskMarkdownService.getData() which reads from ReportStorage.
16766
+ * Returns aggregated metrics calculated from all rejection events.
16767
+ *
16768
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
16769
+ * @param strategyName - Strategy name (e.g., "my-strategy")
16770
+ * @returns Promise resolving to RiskStatistics object with counts and event list
16771
+ *
16772
+ * @example
16773
+ * ```typescript
16774
+ * const stats = await Risk.getData("BTCUSDT", "my-strategy");
16775
+ *
16776
+ * console.log(`Total rejections: ${stats.totalRejections}`);
16777
+ * console.log(`Rejections by symbol:`, stats.bySymbol);
16778
+ * console.log(`Rejections by strategy:`, stats.byStrategy);
16779
+ *
16780
+ * // Iterate through all rejection events
16781
+ * for (const event of stats.eventList) {
16782
+ * console.log(`REJECTED: ${event.symbol} - ${event.comment} (${event.activePositionCount} active)`);
16783
+ * }
16784
+ * ```
16785
+ */
16786
+ this.getData = async (symbol, strategyName) => {
16787
+ backtest$1.loggerService.info(RISK_METHOD_NAME_GET_DATA, { symbol, strategyName });
16788
+ backtest$1.strategyValidationService.validate(strategyName, RISK_METHOD_NAME_GET_DATA);
16789
+ {
16790
+ const { riskName } = backtest$1.strategySchemaService.get(strategyName);
16791
+ riskName && backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_DATA);
16792
+ }
16793
+ return await backtest$1.riskMarkdownService.getData(symbol, strategyName);
16794
+ };
16795
+ /**
16796
+ * Generates markdown report with all risk rejection events for a symbol-strategy pair.
16797
+ *
16798
+ * Creates formatted table containing:
16799
+ * - Symbol
16800
+ * - Strategy
16801
+ * - Position (LONG/SHORT)
16802
+ * - Exchange
16803
+ * - Price
16804
+ * - Active Positions (at rejection time)
16805
+ * - Reason (from validation note)
16806
+ * - Timestamp (ISO 8601)
16807
+ *
16808
+ * Also includes summary statistics at the end (total rejections, by symbol, by strategy).
16809
+ *
16810
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
16811
+ * @param strategyName - Strategy name (e.g., "my-strategy")
16812
+ * @returns Promise resolving to markdown formatted report string
16813
+ *
16814
+ * @example
16815
+ * ```typescript
16816
+ * const markdown = await Risk.getReport("BTCUSDT", "my-strategy");
16817
+ * console.log(markdown);
16818
+ *
16819
+ * // Output:
16820
+ * // # Risk Rejection Report: BTCUSDT:my-strategy
16821
+ * //
16822
+ * // | Symbol | Strategy | Position | Exchange | Price | Active Positions | Reason | Timestamp |
16823
+ * // | --- | --- | --- | --- | --- | --- | --- | --- |
16824
+ * // | BTCUSDT | my-strategy | LONG | binance | 50000.00000000 USD | 3 | Max 3 positions allowed | 2024-01-15T10:30:00.000Z |
16825
+ * //
16826
+ * // **Total rejections:** 1
16827
+ * //
16828
+ * // ## Rejections by Symbol
16829
+ * // - BTCUSDT: 1
16830
+ * //
16831
+ * // ## Rejections by Strategy
16832
+ * // - my-strategy: 1
16833
+ * ```
16834
+ */
16835
+ this.getReport = async (symbol, strategyName) => {
16836
+ backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, { symbol, strategyName });
16837
+ backtest$1.strategyValidationService.validate(strategyName, RISK_METHOD_NAME_GET_REPORT);
16838
+ {
16839
+ const { riskName } = backtest$1.strategySchemaService.get(strategyName);
16840
+ riskName && backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
16841
+ }
16842
+ return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
16843
+ };
16844
+ /**
16845
+ * Generates and saves markdown report to file.
16846
+ *
16847
+ * Creates directory if it doesn't exist.
16848
+ * Filename format: {symbol}_{strategyName}.md (e.g., "BTCUSDT_my-strategy.md")
16849
+ *
16850
+ * Delegates to RiskMarkdownService.dump() which:
16851
+ * 1. Generates markdown report via getReport()
16852
+ * 2. Creates output directory (recursive mkdir)
16853
+ * 3. Writes file with UTF-8 encoding
16854
+ * 4. Logs success/failure to console
16855
+ *
16856
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
16857
+ * @param strategyName - Strategy name (e.g., "my-strategy")
16858
+ * @param path - Output directory path (default: "./dump/risk")
16859
+ * @returns Promise that resolves when file is written
16860
+ *
16861
+ * @example
16862
+ * ```typescript
16863
+ * // Save to default path: ./dump/risk/BTCUSDT_my-strategy.md
16864
+ * await Risk.dump("BTCUSDT", "my-strategy");
16865
+ *
16866
+ * // Save to custom path: ./reports/risk/BTCUSDT_my-strategy.md
16867
+ * await Risk.dump("BTCUSDT", "my-strategy", "./reports/risk");
16868
+ *
16869
+ * // After multiple symbols backtested, export all risk reports
16870
+ * for (const symbol of ["BTCUSDT", "ETHUSDT", "BNBUSDT"]) {
16871
+ * await Risk.dump(symbol, "my-strategy", "./backtest-results");
16872
+ * }
16873
+ * ```
16874
+ */
16875
+ this.dump = async (symbol, strategyName, path) => {
16876
+ backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, { symbol, strategyName, path });
16877
+ backtest$1.strategyValidationService.validate(strategyName, RISK_METHOD_NAME_DUMP);
16878
+ {
16879
+ const { riskName } = backtest$1.strategySchemaService.get(strategyName);
16880
+ riskName && backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
16881
+ }
16882
+ await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
16883
+ };
16884
+ }
16885
+ }
16886
+ /**
16887
+ * Global singleton instance of RiskUtils.
16888
+ * Provides static-like access to risk rejection reporting methods.
16889
+ *
16890
+ * @example
16891
+ * ```typescript
16892
+ * import { Risk } from "backtest-kit";
16893
+ *
16894
+ * // Usage same as RiskUtils methods
16895
+ * const stats = await Risk.getData("BTCUSDT", "my-strategy");
16896
+ * const report = await Risk.getReport("BTCUSDT", "my-strategy");
16897
+ * await Risk.dump("BTCUSDT", "my-strategy");
16898
+ * ```
16899
+ */
16900
+ const Risk = new RiskUtils();
16901
+
16902
+ 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 };