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 +1 -1
- package/build/index.cjs +71 -37
- package/build/index.mjs +72 -38
- package/package.json +1 -1
- package/types.d.ts +33 -9
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
|
|
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
|

|
|
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 =
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
}
|
|
4187
|
-
|
|
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", {
|
|
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
|
-
|
|
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
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
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 (
|
|
4312
|
+
if (rejectionResult) {
|
|
4288
4313
|
// Call params.onRejected for riskSubject emission
|
|
4289
|
-
await this.params.onRejected(params.symbol, params, riskMap.size,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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", "
|
|
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: "
|
|
7123
|
-
label: "
|
|
7124
|
-
format: (data) => data.
|
|
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,
|
|
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 =
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
}
|
|
4185
|
-
|
|
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", {
|
|
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
|
-
|
|
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
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
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 (
|
|
4310
|
+
if (rejectionResult) {
|
|
4286
4311
|
// Call params.onRejected for riskSubject emission
|
|
4287
|
-
await this.params.onRejected(params.symbol, params, riskMap.size,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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", "
|
|
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: "
|
|
7121
|
-
label: "
|
|
7122
|
-
format: (data) => data.
|
|
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
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
|
-
*
|
|
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):
|
|
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
|
|
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,
|
|
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
|
-
*
|
|
3128
|
-
*
|
|
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.
|
|
3154
|
+
* console.log(`Rejection reason: ${event.rejectionNote}`);
|
|
3133
3155
|
* // Output: "Rejection reason: Max 3 positions allowed"
|
|
3134
3156
|
* ```
|
|
3135
3157
|
*/
|
|
3136
|
-
|
|
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
|
-
|
|
4568
|
+
rejectionNote: string;
|
|
4545
4569
|
/** Whether this event is from backtest mode (true) or live mode (false) */
|
|
4546
4570
|
backtest: boolean;
|
|
4547
4571
|
}
|