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