backtest-kit 1.5.44 → 1.5.46

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # 🧿 Backtest Kit
4
4
 
5
- > A TypeScript framework for backtesting and live trading strategies on crypto markets or forex, with crash-safe persistence, signal validation, and AI optimization.
5
+ > A TypeScript framework for backtesting and live trading strategies on multi-asset, crypto or forex, with crash-safe persistence, signal validation, and AI optimization.
6
6
 
7
7
  ![future](./assets/prophet.png)
8
8
 
package/build/index.cjs CHANGED
@@ -4175,17 +4175,29 @@ class SizingConnectionService {
4175
4175
  }
4176
4176
  }
4177
4177
 
4178
+ /**
4179
+ * Retrieves a value from an object using a given path.
4180
+ *
4181
+ * @param object - The object from which to retrieve the value.
4182
+ * @param path - The path to the desired value, either as an array or dot-separated string.
4183
+ * @returns - The value at the specified path, or undefined if it does not exist.
4184
+ */
4185
+ const get = (object, path) => {
4186
+ const pathArray = Array.isArray(path) ? path : path.split('.').filter((key) => key);
4187
+ const pathArrayFlat = pathArray.flatMap((part) => typeof part === 'string' ? part.split('.') : part);
4188
+ return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
4189
+ };
4190
+
4178
4191
  /** Symbol indicating that positions need to be fetched from persistence */
4179
4192
  const POSITION_NEED_FETCH = Symbol("risk-need-fetch");
4180
4193
  /** Key generator for active position map */
4181
4194
  const GET_KEY_FN = (strategyName, symbol) => `${strategyName}:${symbol}`;
4182
4195
  /** Wrapper to execute risk validation function with error handling */
4183
- const DO_VALIDATION_FN = functoolsKit.trycatch(async (validation, params) => {
4184
- await validation(params);
4185
- return true;
4186
- }, {
4187
- defaultValue: false,
4188
- fallback: (error) => {
4196
+ const DO_VALIDATION_FN = async (validation, params) => {
4197
+ try {
4198
+ return await validation(params);
4199
+ }
4200
+ catch (error) {
4189
4201
  const message = "ClientRisk exception thrown";
4190
4202
  const payload = {
4191
4203
  error: functoolsKit.errorData(error),
@@ -4194,8 +4206,9 @@ const DO_VALIDATION_FN = functoolsKit.trycatch(async (validation, params) => {
4194
4206
  backtest$1.loggerService.warn(message, payload);
4195
4207
  console.warn(message, payload);
4196
4208
  validationSubject.next(error);
4197
- },
4198
- });
4209
+ return payload.message;
4210
+ }
4211
+ };
4199
4212
  /**
4200
4213
  * Initializes active positions by reading from persistence.
4201
4214
  * Uses singleshot pattern to ensure it only runs once.
@@ -4204,7 +4217,9 @@ const DO_VALIDATION_FN = functoolsKit.trycatch(async (validation, params) => {
4204
4217
  * In backtest mode, initializes with empty Map. In live mode, reads from persist storage.
4205
4218
  */
4206
4219
  const WAIT_FOR_INIT_FN$1 = async (self) => {
4207
- self.params.logger.debug("ClientRisk waitForInit", { backtest: self.params.backtest });
4220
+ self.params.logger.debug("ClientRisk waitForInit", {
4221
+ backtest: self.params.backtest,
4222
+ });
4208
4223
  if (self.params.backtest) {
4209
4224
  self._activePositions = new Map();
4210
4225
  return;
@@ -4267,26 +4282,36 @@ class ClientRisk {
4267
4282
  activePositionCount: riskMap.size,
4268
4283
  activePositions: Array.from(riskMap.values()),
4269
4284
  };
4270
- // Execute custom validations
4271
- let isValid = true;
4272
- let rejectionNote = "N/A";
4285
+ let rejectionResult = null;
4273
4286
  if (this.params.validations) {
4274
4287
  for (const validation of this.params.validations) {
4275
- if (functoolsKit.not(await DO_VALIDATION_FN(typeof validation === "function"
4276
- ? validation
4277
- : validation.validate, payload))) {
4278
- isValid = false;
4279
- // Capture note from validation if available
4280
- if (typeof validation !== "function" && validation.note) {
4281
- rejectionNote = validation.note;
4282
- }
4288
+ const rejection = await DO_VALIDATION_FN(typeof validation === "function" ? validation : validation.validate, payload);
4289
+ if (!rejection) {
4290
+ continue;
4291
+ }
4292
+ if (typeof rejection === "string") {
4293
+ rejectionResult = {
4294
+ id: null,
4295
+ note: rejection
4296
+ ? rejection
4297
+ : "note" in validation
4298
+ ? validation.note
4299
+ : "Validation failed",
4300
+ };
4301
+ break;
4302
+ }
4303
+ if (functoolsKit.isObject(rejection)) {
4304
+ rejectionResult = {
4305
+ id: get(rejection, "id") || null,
4306
+ note: get(rejection, "note") || "Validation rejected the signal",
4307
+ };
4283
4308
  break;
4284
4309
  }
4285
4310
  }
4286
4311
  }
4287
- if (!isValid) {
4312
+ if (rejectionResult) {
4288
4313
  // Call params.onRejected for riskSubject emission
4289
- await this.params.onRejected(params.symbol, params, riskMap.size, rejectionNote, Date.now(), this.params.backtest);
4314
+ await this.params.onRejected(params.symbol, params, riskMap.size, rejectionResult, params.timestamp, this.params.backtest);
4290
4315
  // Call schema callbacks.onRejected if defined
4291
4316
  if (this.params.callbacks?.onRejected) {
4292
4317
  this.params.callbacks.onRejected(params.symbol, params);
@@ -4365,18 +4390,19 @@ class ClientRisk {
4365
4390
  * @param symbol - Trading pair symbol
4366
4391
  * @param params - Risk check arguments
4367
4392
  * @param activePositionCount - Number of active positions at rejection time
4368
- * @param comment - Rejection reason from validation note or "N/A"
4393
+ * @param rejectionResult - Rejection result with id and note
4369
4394
  * @param timestamp - Event timestamp in milliseconds
4370
4395
  * @param backtest - True if backtest mode, false if live mode
4371
4396
  */
4372
- const COMMIT_REJECTION_FN = async (symbol, params, activePositionCount, comment, timestamp, backtest) => await riskSubject.next({
4397
+ const COMMIT_REJECTION_FN = async (symbol, params, activePositionCount, rejectionResult, timestamp, backtest) => await riskSubject.next({
4373
4398
  symbol,
4374
4399
  pendingSignal: params.pendingSignal,
4375
4400
  strategyName: params.strategyName,
4376
4401
  exchangeName: params.exchangeName,
4377
4402
  currentPrice: params.currentPrice,
4378
4403
  activePositionCount,
4379
- comment,
4404
+ rejectionId: rejectionResult.id,
4405
+ rejectionNote: rejectionResult.note,
4380
4406
  timestamp,
4381
4407
  backtest,
4382
4408
  });
@@ -7019,11 +7045,13 @@ const performance_columns = [
7019
7045
  * - Signal identification (symbol, strategy name, signal ID, position)
7020
7046
  * - Exchange information (exchange name, active position count)
7021
7047
  * - Price data (open price, take profit, stop loss, current price)
7022
- * - Rejection details (rejection reason/comment, timestamp)
7048
+ * - Rejection details (rejection ID, rejection reason, timestamp)
7023
7049
  *
7024
7050
  * @remarks
7025
7051
  * This configuration helps analyze when and why the risk management system rejected signals.
7026
- * The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
7052
+ * - The "note" column (signal note) visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
7053
+ * - The "rejectionNote" column (rejection reason) is always visible as it contains critical risk information.
7054
+ * - The "rejectionId" can be used to correlate rejections with signal IDs for debugging.
7027
7055
  * Useful for tuning risk parameters and understanding risk control effectiveness.
7028
7056
  *
7029
7057
  * @example
@@ -7036,7 +7064,7 @@ const performance_columns = [
7036
7064
  *
7037
7065
  * // Or customize to focus on rejection reasons
7038
7066
  * const customColumns = risk_columns.filter(col =>
7039
- * ["symbol", "strategyName", "comment", "activePositionCount", "timestamp"].includes(col.key)
7067
+ * ["symbol", "strategyName", "rejectionId", "rejectionNote", "activePositionCount", "timestamp"].includes(col.key)
7040
7068
  * );
7041
7069
  * await service.getReport("BTCUSDT", "my-strategy", customColumns);
7042
7070
  * ```
@@ -7064,18 +7092,18 @@ const risk_columns = [
7064
7092
  format: (data) => data.pendingSignal.id || "N/A",
7065
7093
  isVisible: () => true,
7066
7094
  },
7067
- {
7068
- key: "position",
7069
- label: "Position",
7070
- format: (data) => data.pendingSignal.position.toUpperCase(),
7071
- isVisible: () => true,
7072
- },
7073
7095
  {
7074
7096
  key: "note",
7075
7097
  label: "Note",
7076
7098
  format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
7077
7099
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
7078
7100
  },
7101
+ {
7102
+ key: "position",
7103
+ label: "Position",
7104
+ format: (data) => data.pendingSignal.position.toUpperCase(),
7105
+ isVisible: () => true,
7106
+ },
7079
7107
  {
7080
7108
  key: "exchangeName",
7081
7109
  label: "Exchange",
@@ -7119,9 +7147,15 @@ const risk_columns = [
7119
7147
  isVisible: () => true,
7120
7148
  },
7121
7149
  {
7122
- key: "comment",
7123
- label: "Reason",
7124
- format: (data) => data.comment,
7150
+ key: "rejectionId",
7151
+ label: "ID",
7152
+ format: (data) => data.rejectionId ?? "N/A",
7153
+ isVisible: () => true,
7154
+ },
7155
+ {
7156
+ key: "rejectionNote",
7157
+ label: "Rejection Reason",
7158
+ format: (data) => data.rejectionNote,
7125
7159
  isVisible: () => true,
7126
7160
  },
7127
7161
  {
package/build/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createActivator } from 'di-kit';
2
2
  import { scoped } from 'di-scoped';
3
- import { errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, ToolRegistry, isObject, and, resolveDocuments, str, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
3
+ import { errorData, getErrorMessage, sleep, memoize, makeExtendable, singleshot, not, trycatch, retry, Subject, randomString, isObject, ToolRegistry, and, resolveDocuments, str, iterateDocuments, distinctDocuments, queued, singlerun } from 'functools-kit';
4
4
  import fs, { mkdir, writeFile } from 'fs/promises';
5
5
  import path, { join } from 'path';
6
6
  import crypto from 'crypto';
@@ -4173,17 +4173,29 @@ class SizingConnectionService {
4173
4173
  }
4174
4174
  }
4175
4175
 
4176
+ /**
4177
+ * Retrieves a value from an object using a given path.
4178
+ *
4179
+ * @param object - The object from which to retrieve the value.
4180
+ * @param path - The path to the desired value, either as an array or dot-separated string.
4181
+ * @returns - The value at the specified path, or undefined if it does not exist.
4182
+ */
4183
+ const get = (object, path) => {
4184
+ const pathArray = Array.isArray(path) ? path : path.split('.').filter((key) => key);
4185
+ const pathArrayFlat = pathArray.flatMap((part) => typeof part === 'string' ? part.split('.') : part);
4186
+ return pathArrayFlat.reduce((obj, key) => obj && obj[key], object);
4187
+ };
4188
+
4176
4189
  /** Symbol indicating that positions need to be fetched from persistence */
4177
4190
  const POSITION_NEED_FETCH = Symbol("risk-need-fetch");
4178
4191
  /** Key generator for active position map */
4179
4192
  const GET_KEY_FN = (strategyName, symbol) => `${strategyName}:${symbol}`;
4180
4193
  /** Wrapper to execute risk validation function with error handling */
4181
- const DO_VALIDATION_FN = trycatch(async (validation, params) => {
4182
- await validation(params);
4183
- return true;
4184
- }, {
4185
- defaultValue: false,
4186
- fallback: (error) => {
4194
+ const DO_VALIDATION_FN = async (validation, params) => {
4195
+ try {
4196
+ return await validation(params);
4197
+ }
4198
+ catch (error) {
4187
4199
  const message = "ClientRisk exception thrown";
4188
4200
  const payload = {
4189
4201
  error: errorData(error),
@@ -4192,8 +4204,9 @@ const DO_VALIDATION_FN = trycatch(async (validation, params) => {
4192
4204
  backtest$1.loggerService.warn(message, payload);
4193
4205
  console.warn(message, payload);
4194
4206
  validationSubject.next(error);
4195
- },
4196
- });
4207
+ return payload.message;
4208
+ }
4209
+ };
4197
4210
  /**
4198
4211
  * Initializes active positions by reading from persistence.
4199
4212
  * Uses singleshot pattern to ensure it only runs once.
@@ -4202,7 +4215,9 @@ const DO_VALIDATION_FN = trycatch(async (validation, params) => {
4202
4215
  * In backtest mode, initializes with empty Map. In live mode, reads from persist storage.
4203
4216
  */
4204
4217
  const WAIT_FOR_INIT_FN$1 = async (self) => {
4205
- self.params.logger.debug("ClientRisk waitForInit", { backtest: self.params.backtest });
4218
+ self.params.logger.debug("ClientRisk waitForInit", {
4219
+ backtest: self.params.backtest,
4220
+ });
4206
4221
  if (self.params.backtest) {
4207
4222
  self._activePositions = new Map();
4208
4223
  return;
@@ -4265,26 +4280,36 @@ class ClientRisk {
4265
4280
  activePositionCount: riskMap.size,
4266
4281
  activePositions: Array.from(riskMap.values()),
4267
4282
  };
4268
- // Execute custom validations
4269
- let isValid = true;
4270
- let rejectionNote = "N/A";
4283
+ let rejectionResult = null;
4271
4284
  if (this.params.validations) {
4272
4285
  for (const validation of this.params.validations) {
4273
- if (not(await DO_VALIDATION_FN(typeof validation === "function"
4274
- ? validation
4275
- : validation.validate, payload))) {
4276
- isValid = false;
4277
- // Capture note from validation if available
4278
- if (typeof validation !== "function" && validation.note) {
4279
- rejectionNote = validation.note;
4280
- }
4286
+ const rejection = await DO_VALIDATION_FN(typeof validation === "function" ? validation : validation.validate, payload);
4287
+ if (!rejection) {
4288
+ continue;
4289
+ }
4290
+ if (typeof rejection === "string") {
4291
+ rejectionResult = {
4292
+ id: null,
4293
+ note: rejection
4294
+ ? rejection
4295
+ : "note" in validation
4296
+ ? validation.note
4297
+ : "Validation failed",
4298
+ };
4299
+ break;
4300
+ }
4301
+ if (isObject(rejection)) {
4302
+ rejectionResult = {
4303
+ id: get(rejection, "id") || null,
4304
+ note: get(rejection, "note") || "Validation rejected the signal",
4305
+ };
4281
4306
  break;
4282
4307
  }
4283
4308
  }
4284
4309
  }
4285
- if (!isValid) {
4310
+ if (rejectionResult) {
4286
4311
  // Call params.onRejected for riskSubject emission
4287
- await this.params.onRejected(params.symbol, params, riskMap.size, rejectionNote, Date.now(), this.params.backtest);
4312
+ await this.params.onRejected(params.symbol, params, riskMap.size, rejectionResult, params.timestamp, this.params.backtest);
4288
4313
  // Call schema callbacks.onRejected if defined
4289
4314
  if (this.params.callbacks?.onRejected) {
4290
4315
  this.params.callbacks.onRejected(params.symbol, params);
@@ -4363,18 +4388,19 @@ class ClientRisk {
4363
4388
  * @param symbol - Trading pair symbol
4364
4389
  * @param params - Risk check arguments
4365
4390
  * @param activePositionCount - Number of active positions at rejection time
4366
- * @param comment - Rejection reason from validation note or "N/A"
4391
+ * @param rejectionResult - Rejection result with id and note
4367
4392
  * @param timestamp - Event timestamp in milliseconds
4368
4393
  * @param backtest - True if backtest mode, false if live mode
4369
4394
  */
4370
- const COMMIT_REJECTION_FN = async (symbol, params, activePositionCount, comment, timestamp, backtest) => await riskSubject.next({
4395
+ const COMMIT_REJECTION_FN = async (symbol, params, activePositionCount, rejectionResult, timestamp, backtest) => await riskSubject.next({
4371
4396
  symbol,
4372
4397
  pendingSignal: params.pendingSignal,
4373
4398
  strategyName: params.strategyName,
4374
4399
  exchangeName: params.exchangeName,
4375
4400
  currentPrice: params.currentPrice,
4376
4401
  activePositionCount,
4377
- comment,
4402
+ rejectionId: rejectionResult.id,
4403
+ rejectionNote: rejectionResult.note,
4378
4404
  timestamp,
4379
4405
  backtest,
4380
4406
  });
@@ -7017,11 +7043,13 @@ const performance_columns = [
7017
7043
  * - Signal identification (symbol, strategy name, signal ID, position)
7018
7044
  * - Exchange information (exchange name, active position count)
7019
7045
  * - Price data (open price, take profit, stop loss, current price)
7020
- * - Rejection details (rejection reason/comment, timestamp)
7046
+ * - Rejection details (rejection ID, rejection reason, timestamp)
7021
7047
  *
7022
7048
  * @remarks
7023
7049
  * This configuration helps analyze when and why the risk management system rejected signals.
7024
- * The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
7050
+ * - The "note" column (signal note) visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
7051
+ * - The "rejectionNote" column (rejection reason) is always visible as it contains critical risk information.
7052
+ * - The "rejectionId" can be used to correlate rejections with signal IDs for debugging.
7025
7053
  * Useful for tuning risk parameters and understanding risk control effectiveness.
7026
7054
  *
7027
7055
  * @example
@@ -7034,7 +7062,7 @@ const performance_columns = [
7034
7062
  *
7035
7063
  * // Or customize to focus on rejection reasons
7036
7064
  * const customColumns = risk_columns.filter(col =>
7037
- * ["symbol", "strategyName", "comment", "activePositionCount", "timestamp"].includes(col.key)
7065
+ * ["symbol", "strategyName", "rejectionId", "rejectionNote", "activePositionCount", "timestamp"].includes(col.key)
7038
7066
  * );
7039
7067
  * await service.getReport("BTCUSDT", "my-strategy", customColumns);
7040
7068
  * ```
@@ -7062,18 +7090,18 @@ const risk_columns = [
7062
7090
  format: (data) => data.pendingSignal.id || "N/A",
7063
7091
  isVisible: () => true,
7064
7092
  },
7065
- {
7066
- key: "position",
7067
- label: "Position",
7068
- format: (data) => data.pendingSignal.position.toUpperCase(),
7069
- isVisible: () => true,
7070
- },
7071
7093
  {
7072
7094
  key: "note",
7073
7095
  label: "Note",
7074
7096
  format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
7075
7097
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
7076
7098
  },
7099
+ {
7100
+ key: "position",
7101
+ label: "Position",
7102
+ format: (data) => data.pendingSignal.position.toUpperCase(),
7103
+ isVisible: () => true,
7104
+ },
7077
7105
  {
7078
7106
  key: "exchangeName",
7079
7107
  label: "Exchange",
@@ -7117,9 +7145,15 @@ const risk_columns = [
7117
7145
  isVisible: () => true,
7118
7146
  },
7119
7147
  {
7120
- key: "comment",
7121
- label: "Reason",
7122
- format: (data) => data.comment,
7148
+ key: "rejectionId",
7149
+ label: "ID",
7150
+ format: (data) => data.rejectionId ?? "N/A",
7151
+ isVisible: () => true,
7152
+ },
7153
+ {
7154
+ key: "rejectionNote",
7155
+ label: "Rejection Reason",
7156
+ format: (data) => data.rejectionNote,
7123
7157
  isVisible: () => true,
7124
7158
  },
7125
7159
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backtest-kit",
3
- "version": "1.5.44",
3
+ "version": "1.5.46",
4
4
  "description": "A TypeScript library for trading system backtest",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
package/types.d.ts CHANGED
@@ -759,6 +759,11 @@ declare const MethodContextService: (new () => {
759
759
  };
760
760
  }, "prototype"> & di_scoped.IScopedClassRun<[context: IMethodContext]>;
761
761
 
762
+ /**
763
+ * Risk rejection result type.
764
+ * Can be void, null, or an IRiskRejectionResult object.
765
+ */
766
+ type RiskRejection = void | IRiskRejectionResult | string | null;
762
767
  /**
763
768
  * Risk check arguments for evaluating whether to allow opening a new position.
764
769
  * Called BEFORE signal creation to validate if conditions allow new signals.
@@ -812,12 +817,23 @@ interface IRiskValidationPayload extends IRiskCheckArgs {
812
817
  /** List of currently active positions across all strategies */
813
818
  activePositions: IRiskActivePosition[];
814
819
  }
820
+ /**
821
+ * Risk validation rejection result.
822
+ * Returned when validation fails, contains debugging information.
823
+ */
824
+ interface IRiskRejectionResult {
825
+ /** Unique identifier for this rejection instance */
826
+ id: string | null;
827
+ /** Human-readable reason for rejection */
828
+ note: string;
829
+ }
815
830
  /**
816
831
  * Risk validation function type.
817
- * Validates risk parameters and throws error if validation fails.
832
+ * Returns null/void if validation passes, IRiskRejectionResult if validation fails.
833
+ * Can also throw error which will be caught and converted to IRiskRejectionResult.
818
834
  */
819
835
  interface IRiskValidationFn {
820
- (payload: IRiskValidationPayload): void | Promise<void>;
836
+ (payload: IRiskValidationPayload): RiskRejection | Promise<RiskRejection>;
821
837
  }
822
838
  /**
823
839
  * Risk validation configuration.
@@ -865,11 +881,11 @@ interface IRiskParams extends IRiskSchema {
865
881
  * @param symbol - Trading pair symbol
866
882
  * @param params - Risk check arguments
867
883
  * @param activePositionCount - Number of active positions at rejection time
868
- * @param comment - Rejection reason from validation note or "N/A"
884
+ * @param rejectionResult - Rejection result with id and note
869
885
  * @param timestamp - Event timestamp in milliseconds
870
886
  * @param backtest - True if backtest mode, false if live mode
871
887
  */
872
- onRejected: (symbol: string, params: IRiskCheckArgs, activePositionCount: number, comment: string, timestamp: number, backtest: boolean) => void | Promise<void>;
888
+ onRejected: (symbol: string, params: IRiskCheckArgs, activePositionCount: number, rejectionResult: IRiskRejectionResult, timestamp: number, backtest: boolean) => void | Promise<void>;
873
889
  }
874
890
  /**
875
891
  * Risk interface implemented by ClientRisk.
@@ -3124,16 +3140,22 @@ interface RiskContract {
3124
3140
  */
3125
3141
  activePositionCount: number;
3126
3142
  /**
3127
- * Comment describing why the signal was rejected.
3128
- * Captured from IRiskValidation.note or "N/A" if not provided.
3143
+ * Unique identifier for this rejection instance.
3144
+ * Generated by ClientRisk for tracking and debugging purposes.
3145
+ * Null if validation threw exception without custom ID.
3146
+ */
3147
+ rejectionId: string | null;
3148
+ /**
3149
+ * Human-readable reason why the signal was rejected.
3150
+ * Captured from IRiskValidation.note or error message.
3129
3151
  *
3130
3152
  * @example
3131
3153
  * ```typescript
3132
- * console.log(`Rejection reason: ${event.comment}`);
3154
+ * console.log(`Rejection reason: ${event.rejectionNote}`);
3133
3155
  * // Output: "Rejection reason: Max 3 positions allowed"
3134
3156
  * ```
3135
3157
  */
3136
- comment: string;
3158
+ rejectionNote: string;
3137
3159
  /**
3138
3160
  * Event timestamp in milliseconds since Unix epoch.
3139
3161
  * Represents when the signal was rejected.
@@ -4540,8 +4562,10 @@ interface RiskEvent {
4540
4562
  currentPrice: number;
4541
4563
  /** Number of active positions at rejection time */
4542
4564
  activePositionCount: number;
4565
+ /** Unique identifier for this rejection instance (null if validation threw exception without custom ID) */
4566
+ rejectionId: string | null;
4543
4567
  /** Rejection reason from validation note */
4544
- comment: string;
4568
+ rejectionNote: string;
4545
4569
  /** Whether this event is from backtest mode (true) or live mode (false) */
4546
4570
  backtest: boolean;
4547
4571
  }