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