backtest-kit 1.5.25 → 1.5.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/build/index.cjs +1789 -1055
- package/build/index.mjs +1788 -1057
- package/package.json +1 -1
- package/types.d.ts +4549 -3961
package/build/index.cjs
CHANGED
|
@@ -122,6 +122,1081 @@ const GLOBAL_CONFIG = {
|
|
|
122
122
|
};
|
|
123
123
|
const DEFAULT_CONFIG = Object.freeze({ ...GLOBAL_CONFIG });
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Converts markdown content to plain text with minimal formatting
|
|
127
|
+
* @param content - Markdown string to convert
|
|
128
|
+
* @returns Plain text representation
|
|
129
|
+
*/
|
|
130
|
+
const toPlainString = (content) => {
|
|
131
|
+
if (!content) {
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
let text = content;
|
|
135
|
+
// Remove code blocks
|
|
136
|
+
text = text.replace(/```[\s\S]*?```/g, "");
|
|
137
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
138
|
+
// Remove images
|
|
139
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
140
|
+
// Convert links to text only (keep link text, remove URL)
|
|
141
|
+
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
142
|
+
// Remove headers (convert to plain text)
|
|
143
|
+
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
144
|
+
// Remove bold and italic markers
|
|
145
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
146
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
147
|
+
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
148
|
+
text = text.replace(/___(.+?)___/g, "$1");
|
|
149
|
+
text = text.replace(/__(.+?)__/g, "$1");
|
|
150
|
+
text = text.replace(/_(.+?)_/g, "$1");
|
|
151
|
+
// Remove strikethrough
|
|
152
|
+
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
153
|
+
// Convert lists to plain text with bullets
|
|
154
|
+
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
155
|
+
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
156
|
+
// Remove blockquotes
|
|
157
|
+
text = text.replace(/^\s*>\s+/gm, "");
|
|
158
|
+
// Remove horizontal rules
|
|
159
|
+
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
160
|
+
// Remove HTML tags
|
|
161
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
162
|
+
// Remove excessive whitespace and normalize line breaks
|
|
163
|
+
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
164
|
+
text = text.replace(/[ \t]+/g, " ");
|
|
165
|
+
// Remove all newline characters
|
|
166
|
+
text = text.replace(/\n/g, " ");
|
|
167
|
+
// Remove excessive spaces after newline removal
|
|
168
|
+
text = text.replace(/\s+/g, " ");
|
|
169
|
+
return text.trim();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Column configuration for backtest markdown reports.
|
|
174
|
+
*
|
|
175
|
+
* Defines the table structure for displaying closed trading signals in backtest reports.
|
|
176
|
+
* Each column specifies how to format and display specific signal data fields.
|
|
177
|
+
*
|
|
178
|
+
* Used by {@link BacktestMarkdownService} to generate markdown tables showing:
|
|
179
|
+
* - Signal identification (ID, symbol, position)
|
|
180
|
+
* - Price levels (open, close, take profit, stop loss)
|
|
181
|
+
* - Performance metrics (PNL percentage, close reason)
|
|
182
|
+
* - Timing information (duration, timestamps)
|
|
183
|
+
*
|
|
184
|
+
* @remarks
|
|
185
|
+
* Columns can be conditionally visible based on {@link GLOBAL_CONFIG} settings.
|
|
186
|
+
* For example, the "note" column visibility is controlled by `CC_REPORT_SHOW_SIGNAL_NOTE`.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* import { backtest_columns } from "./assets/backtest.columns";
|
|
191
|
+
*
|
|
192
|
+
* // Use with BacktestMarkdownService
|
|
193
|
+
* const service = new BacktestMarkdownService();
|
|
194
|
+
* await service.getReport("BTCUSDT", "my-strategy", backtest_columns);
|
|
195
|
+
*
|
|
196
|
+
* // Or customize columns
|
|
197
|
+
* const customColumns = backtest_columns.filter(col => col.key !== "note");
|
|
198
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* @see {@link BacktestMarkdownService} for usage in report generation
|
|
202
|
+
* @see {@link ColumnModel} for column interface definition
|
|
203
|
+
* @see {@link IStrategyTickResultClosed} for data structure
|
|
204
|
+
*/
|
|
205
|
+
const backtest_columns = [
|
|
206
|
+
{
|
|
207
|
+
key: "signalId",
|
|
208
|
+
label: "Signal ID",
|
|
209
|
+
format: (data) => data.signal.id,
|
|
210
|
+
isVisible: () => true,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
key: "symbol",
|
|
214
|
+
label: "Symbol",
|
|
215
|
+
format: (data) => data.signal.symbol,
|
|
216
|
+
isVisible: () => true,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
key: "position",
|
|
220
|
+
label: "Position",
|
|
221
|
+
format: (data) => data.signal.position.toUpperCase(),
|
|
222
|
+
isVisible: () => true,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
key: "note",
|
|
226
|
+
label: "Note",
|
|
227
|
+
format: (data) => toPlainString(data.signal.note ?? "N/A"),
|
|
228
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
key: "openPrice",
|
|
232
|
+
label: "Open Price",
|
|
233
|
+
format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
|
|
234
|
+
isVisible: () => true,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
key: "closePrice",
|
|
238
|
+
label: "Close Price",
|
|
239
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
240
|
+
isVisible: () => true,
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
key: "takeProfit",
|
|
244
|
+
label: "Take Profit",
|
|
245
|
+
format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
|
|
246
|
+
isVisible: () => true,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
key: "stopLoss",
|
|
250
|
+
label: "Stop Loss",
|
|
251
|
+
format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
|
|
252
|
+
isVisible: () => true,
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
key: "pnl",
|
|
256
|
+
label: "PNL (net)",
|
|
257
|
+
format: (data) => {
|
|
258
|
+
const pnlPercentage = data.pnl.pnlPercentage;
|
|
259
|
+
return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
|
|
260
|
+
},
|
|
261
|
+
isVisible: () => true,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
key: "closeReason",
|
|
265
|
+
label: "Close Reason",
|
|
266
|
+
format: (data) => data.closeReason,
|
|
267
|
+
isVisible: () => true,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
key: "duration",
|
|
271
|
+
label: "Duration (min)",
|
|
272
|
+
format: (data) => {
|
|
273
|
+
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
274
|
+
const durationMin = Math.round(durationMs / 60000);
|
|
275
|
+
return `${durationMin}`;
|
|
276
|
+
},
|
|
277
|
+
isVisible: () => true,
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
key: "openTimestamp",
|
|
281
|
+
label: "Open Time",
|
|
282
|
+
format: (data) => new Date(data.signal.pendingAt).toISOString(),
|
|
283
|
+
isVisible: () => true,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
key: "closeTimestamp",
|
|
287
|
+
label: "Close Time",
|
|
288
|
+
format: (data) => new Date(data.closeTimestamp).toISOString(),
|
|
289
|
+
isVisible: () => true,
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Column configuration for portfolio heatmap markdown reports.
|
|
295
|
+
*
|
|
296
|
+
* Defines the table structure for displaying aggregated per-symbol statistics in portfolio heatmap reports.
|
|
297
|
+
* Each column specifies how to format and display portfolio performance metrics across different trading symbols.
|
|
298
|
+
*
|
|
299
|
+
* Used by {@link HeatMarkdownService} to generate markdown tables showing:
|
|
300
|
+
* - Symbol identification
|
|
301
|
+
* - Performance metrics (Total PNL, Sharpe Ratio, Profit Factor, Expectancy)
|
|
302
|
+
* - Risk metrics (Win Rate, Average Win/Loss, Max Drawdown)
|
|
303
|
+
* - Trading activity (Total Trades, Win/Loss Streaks)
|
|
304
|
+
*
|
|
305
|
+
* @remarks
|
|
306
|
+
* This configuration is used to create portfolio-wide views that aggregate statistics per symbol.
|
|
307
|
+
* The heatmap service automatically sorts symbols by Sharpe Ratio (best performers first).
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* import { heat_columns } from "./assets/heat.columns";
|
|
312
|
+
*
|
|
313
|
+
* // Use with HeatMarkdownService
|
|
314
|
+
* const service = new HeatMarkdownService();
|
|
315
|
+
* await service.getReport("my-strategy", heat_columns);
|
|
316
|
+
*
|
|
317
|
+
* // Or customize to show only key metrics
|
|
318
|
+
* const customColumns = heat_columns.filter(col =>
|
|
319
|
+
* ["symbol", "totalPnl", "sharpeRatio", "totalTrades"].includes(col.key)
|
|
320
|
+
* );
|
|
321
|
+
* await service.getReport("my-strategy", customColumns);
|
|
322
|
+
* ```
|
|
323
|
+
*
|
|
324
|
+
* @see {@link HeatMarkdownService} for usage in report generation
|
|
325
|
+
* @see {@link ColumnModel} for column interface definition
|
|
326
|
+
* @see {@link IHeatmapRow} for data structure
|
|
327
|
+
*/
|
|
328
|
+
const heat_columns = [
|
|
329
|
+
{
|
|
330
|
+
key: "symbol",
|
|
331
|
+
label: "Symbol",
|
|
332
|
+
format: (data) => data.symbol,
|
|
333
|
+
isVisible: () => true,
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
key: "totalPnl",
|
|
337
|
+
label: "Total PNL",
|
|
338
|
+
format: (data) => data.totalPnl !== null ? functoolsKit.str(data.totalPnl, "%+.2f%%") : "N/A",
|
|
339
|
+
isVisible: () => true,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
key: "sharpeRatio",
|
|
343
|
+
label: "Sharpe",
|
|
344
|
+
format: (data) => data.sharpeRatio !== null ? functoolsKit.str(data.sharpeRatio, "%.2f") : "N/A",
|
|
345
|
+
isVisible: () => true,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
key: "profitFactor",
|
|
349
|
+
label: "PF",
|
|
350
|
+
format: (data) => data.profitFactor !== null ? functoolsKit.str(data.profitFactor, "%.2f") : "N/A",
|
|
351
|
+
isVisible: () => true,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
key: "expectancy",
|
|
355
|
+
label: "Expect",
|
|
356
|
+
format: (data) => data.expectancy !== null ? functoolsKit.str(data.expectancy, "%+.2f%%") : "N/A",
|
|
357
|
+
isVisible: () => true,
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
key: "winRate",
|
|
361
|
+
label: "WR",
|
|
362
|
+
format: (data) => data.winRate !== null ? functoolsKit.str(data.winRate, "%.1f%%") : "N/A",
|
|
363
|
+
isVisible: () => true,
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
key: "avgWin",
|
|
367
|
+
label: "Avg Win",
|
|
368
|
+
format: (data) => data.avgWin !== null ? functoolsKit.str(data.avgWin, "%+.2f%%") : "N/A",
|
|
369
|
+
isVisible: () => true,
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
key: "avgLoss",
|
|
373
|
+
label: "Avg Loss",
|
|
374
|
+
format: (data) => data.avgLoss !== null ? functoolsKit.str(data.avgLoss, "%+.2f%%") : "N/A",
|
|
375
|
+
isVisible: () => true,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
key: "maxDrawdown",
|
|
379
|
+
label: "Max DD",
|
|
380
|
+
format: (data) => data.maxDrawdown !== null ? functoolsKit.str(-data.maxDrawdown, "%.2f%%") : "N/A",
|
|
381
|
+
isVisible: () => true,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
key: "maxWinStreak",
|
|
385
|
+
label: "W Streak",
|
|
386
|
+
format: (data) => data.maxWinStreak.toString(),
|
|
387
|
+
isVisible: () => true,
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
key: "maxLossStreak",
|
|
391
|
+
label: "L Streak",
|
|
392
|
+
format: (data) => data.maxLossStreak.toString(),
|
|
393
|
+
isVisible: () => true,
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
key: "totalTrades",
|
|
397
|
+
label: "Trades",
|
|
398
|
+
format: (data) => data.totalTrades.toString(),
|
|
399
|
+
isVisible: () => true,
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Column configuration for live trading markdown reports.
|
|
405
|
+
*
|
|
406
|
+
* Defines the table structure for displaying real-time trading events in live trading reports.
|
|
407
|
+
* Each column specifies how to format and display live trading event data fields.
|
|
408
|
+
*
|
|
409
|
+
* Used by {@link LiveMarkdownService} to generate markdown tables showing:
|
|
410
|
+
* - Event information (timestamp, action type)
|
|
411
|
+
* - Signal identification (symbol, signal ID, position)
|
|
412
|
+
* - Price data (current price, open price, take profit, stop loss)
|
|
413
|
+
* - Performance metrics (PNL percentage, close reason, duration)
|
|
414
|
+
* - Position tracking (percentage to TP/SL)
|
|
415
|
+
*
|
|
416
|
+
* @remarks
|
|
417
|
+
* This configuration tracks all event types: idle, opened, active, and closed signals.
|
|
418
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```typescript
|
|
422
|
+
* import { live_columns } from "./assets/live.columns";
|
|
423
|
+
*
|
|
424
|
+
* // Use with LiveMarkdownService
|
|
425
|
+
* const service = new LiveMarkdownService();
|
|
426
|
+
* await service.getReport("BTCUSDT", "my-strategy", live_columns);
|
|
427
|
+
*
|
|
428
|
+
* // Or customize for minimal display
|
|
429
|
+
* const customColumns = live_columns.filter(col =>
|
|
430
|
+
* ["timestamp", "action", "symbol", "pnl"].includes(col.key)
|
|
431
|
+
* );
|
|
432
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
433
|
+
* ```
|
|
434
|
+
*
|
|
435
|
+
* @see {@link LiveMarkdownService} for usage in report generation
|
|
436
|
+
* @see {@link ColumnModel} for column interface definition
|
|
437
|
+
* @see {@link TickEvent} for data structure
|
|
438
|
+
*/
|
|
439
|
+
const live_columns = [
|
|
440
|
+
{
|
|
441
|
+
key: "timestamp",
|
|
442
|
+
label: "Timestamp",
|
|
443
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
444
|
+
isVisible: () => true,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
key: "action",
|
|
448
|
+
label: "Action",
|
|
449
|
+
format: (data) => data.action.toUpperCase(),
|
|
450
|
+
isVisible: () => true,
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
key: "symbol",
|
|
454
|
+
label: "Symbol",
|
|
455
|
+
format: (data) => data.symbol ?? "N/A",
|
|
456
|
+
isVisible: () => true,
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
key: "signalId",
|
|
460
|
+
label: "Signal ID",
|
|
461
|
+
format: (data) => data.signalId ?? "N/A",
|
|
462
|
+
isVisible: () => true,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
key: "position",
|
|
466
|
+
label: "Position",
|
|
467
|
+
format: (data) => data.position?.toUpperCase() ?? "N/A",
|
|
468
|
+
isVisible: () => true,
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
key: "note",
|
|
472
|
+
label: "Note",
|
|
473
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
474
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
key: "currentPrice",
|
|
478
|
+
label: "Current Price",
|
|
479
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
480
|
+
isVisible: () => true,
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
key: "openPrice",
|
|
484
|
+
label: "Open Price",
|
|
485
|
+
format: (data) => data.openPrice !== undefined ? `${data.openPrice.toFixed(8)} USD` : "N/A",
|
|
486
|
+
isVisible: () => true,
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
key: "takeProfit",
|
|
490
|
+
label: "Take Profit",
|
|
491
|
+
format: (data) => data.takeProfit !== undefined
|
|
492
|
+
? `${data.takeProfit.toFixed(8)} USD`
|
|
493
|
+
: "N/A",
|
|
494
|
+
isVisible: () => true,
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
key: "stopLoss",
|
|
498
|
+
label: "Stop Loss",
|
|
499
|
+
format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
|
|
500
|
+
isVisible: () => true,
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
key: "percentTp",
|
|
504
|
+
label: "% to TP",
|
|
505
|
+
format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
|
|
506
|
+
isVisible: () => true,
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
key: "percentSl",
|
|
510
|
+
label: "% to SL",
|
|
511
|
+
format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
|
|
512
|
+
isVisible: () => true,
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
key: "pnl",
|
|
516
|
+
label: "PNL (net)",
|
|
517
|
+
format: (data) => {
|
|
518
|
+
if (data.pnl === undefined)
|
|
519
|
+
return "N/A";
|
|
520
|
+
return `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`;
|
|
521
|
+
},
|
|
522
|
+
isVisible: () => true,
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
key: "closeReason",
|
|
526
|
+
label: "Close Reason",
|
|
527
|
+
format: (data) => data.closeReason ?? "N/A",
|
|
528
|
+
isVisible: () => true,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
key: "duration",
|
|
532
|
+
label: "Duration (min)",
|
|
533
|
+
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
534
|
+
isVisible: () => true,
|
|
535
|
+
},
|
|
536
|
+
];
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Column configuration for partial profit/loss markdown reports.
|
|
540
|
+
*
|
|
541
|
+
* Defines the table structure for displaying partial position exit events in trading reports.
|
|
542
|
+
* Each column specifies how to format and display partial profit and loss level events.
|
|
543
|
+
*
|
|
544
|
+
* Used by {@link PartialMarkdownService} to generate markdown tables showing:
|
|
545
|
+
* - Event information (action type: profit or loss)
|
|
546
|
+
* - Signal identification (symbol, strategy name, signal ID, position)
|
|
547
|
+
* - Exit level information (percentage level reached)
|
|
548
|
+
* - Price data (current price at partial exit)
|
|
549
|
+
* - Timing information (timestamp, mode: backtest or live)
|
|
550
|
+
*
|
|
551
|
+
* @remarks
|
|
552
|
+
* This configuration tracks partial position exits at predefined profit/loss levels.
|
|
553
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
554
|
+
* Useful for analyzing risk management strategies and partial exit performance.
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```typescript
|
|
558
|
+
* import { partial_columns } from "./assets/partial.columns";
|
|
559
|
+
*
|
|
560
|
+
* // Use with PartialMarkdownService
|
|
561
|
+
* const service = new PartialMarkdownService();
|
|
562
|
+
* await service.getReport("BTCUSDT", "my-strategy", partial_columns);
|
|
563
|
+
*
|
|
564
|
+
* // Or customize to show only key fields
|
|
565
|
+
* const customColumns = partial_columns.filter(col =>
|
|
566
|
+
* ["action", "symbol", "level", "timestamp"].includes(col.key)
|
|
567
|
+
* );
|
|
568
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
569
|
+
* ```
|
|
570
|
+
*
|
|
571
|
+
* @see {@link PartialMarkdownService} for usage in report generation
|
|
572
|
+
* @see {@link ColumnModel} for column interface definition
|
|
573
|
+
* @see {@link PartialEvent} for data structure
|
|
574
|
+
*/
|
|
575
|
+
const partial_columns = [
|
|
576
|
+
{
|
|
577
|
+
key: "action",
|
|
578
|
+
label: "Action",
|
|
579
|
+
format: (data) => data.action.toUpperCase(),
|
|
580
|
+
isVisible: () => true,
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
key: "symbol",
|
|
584
|
+
label: "Symbol",
|
|
585
|
+
format: (data) => data.symbol,
|
|
586
|
+
isVisible: () => true,
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
key: "strategyName",
|
|
590
|
+
label: "Strategy",
|
|
591
|
+
format: (data) => data.strategyName,
|
|
592
|
+
isVisible: () => true,
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
key: "signalId",
|
|
596
|
+
label: "Signal ID",
|
|
597
|
+
format: (data) => data.signalId,
|
|
598
|
+
isVisible: () => true,
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
key: "position",
|
|
602
|
+
label: "Position",
|
|
603
|
+
format: (data) => data.position.toUpperCase(),
|
|
604
|
+
isVisible: () => true,
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
key: "level",
|
|
608
|
+
label: "Level %",
|
|
609
|
+
format: (data) => data.action === "profit" ? `+${data.level}%` : `-${data.level}%`,
|
|
610
|
+
isVisible: () => true,
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
key: "currentPrice",
|
|
614
|
+
label: "Current Price",
|
|
615
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
616
|
+
isVisible: () => true,
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
key: "timestamp",
|
|
620
|
+
label: "Timestamp",
|
|
621
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
622
|
+
isVisible: () => true,
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
key: "mode",
|
|
626
|
+
label: "Mode",
|
|
627
|
+
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
628
|
+
isVisible: () => true,
|
|
629
|
+
},
|
|
630
|
+
];
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Column configuration for performance metrics markdown reports.
|
|
634
|
+
*
|
|
635
|
+
* Defines the table structure for displaying performance statistics and timing metrics in performance reports.
|
|
636
|
+
* Each column specifies how to format and display aggregated performance data for different metric types.
|
|
637
|
+
*
|
|
638
|
+
* Used by {@link PerformanceMarkdownService} to generate markdown tables showing:
|
|
639
|
+
* - Metric identification (metric type)
|
|
640
|
+
* - Execution statistics (count, total duration, average, min, max)
|
|
641
|
+
* - Distribution metrics (standard deviation, median, percentiles)
|
|
642
|
+
* - Wait time statistics (average, min, max wait times between events)
|
|
643
|
+
*
|
|
644
|
+
* @remarks
|
|
645
|
+
* This configuration helps identify performance bottlenecks in strategy execution.
|
|
646
|
+
* The service automatically sorts metrics by total duration to highlight the slowest operations.
|
|
647
|
+
* All durations are measured in milliseconds.
|
|
648
|
+
*
|
|
649
|
+
* @example
|
|
650
|
+
* ```typescript
|
|
651
|
+
* import { performance_columns } from "./assets/performance.columns";
|
|
652
|
+
*
|
|
653
|
+
* // Use with PerformanceMarkdownService
|
|
654
|
+
* const service = new PerformanceMarkdownService();
|
|
655
|
+
* await service.getReport("BTCUSDT", "my-strategy", performance_columns);
|
|
656
|
+
*
|
|
657
|
+
* // Or customize for bottleneck analysis
|
|
658
|
+
* const customColumns = performance_columns.filter(col =>
|
|
659
|
+
* ["metricType", "count", "avgDuration", "maxDuration", "p95"].includes(col.key)
|
|
660
|
+
* );
|
|
661
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
662
|
+
* ```
|
|
663
|
+
*
|
|
664
|
+
* @see {@link PerformanceMarkdownService} for usage in report generation
|
|
665
|
+
* @see {@link ColumnModel} for column interface definition
|
|
666
|
+
* @see {@link MetricStats} for data structure
|
|
667
|
+
*/
|
|
668
|
+
const performance_columns = [
|
|
669
|
+
{
|
|
670
|
+
key: "metricType",
|
|
671
|
+
label: "Metric Type",
|
|
672
|
+
format: (data) => data.metricType,
|
|
673
|
+
isVisible: () => true,
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
key: "count",
|
|
677
|
+
label: "Count",
|
|
678
|
+
format: (data) => data.count.toString(),
|
|
679
|
+
isVisible: () => true,
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
key: "totalDuration",
|
|
683
|
+
label: "Total (ms)",
|
|
684
|
+
format: (data) => data.totalDuration.toFixed(2),
|
|
685
|
+
isVisible: () => true,
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
key: "avgDuration",
|
|
689
|
+
label: "Avg (ms)",
|
|
690
|
+
format: (data) => data.avgDuration.toFixed(2),
|
|
691
|
+
isVisible: () => true,
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
key: "minDuration",
|
|
695
|
+
label: "Min (ms)",
|
|
696
|
+
format: (data) => data.minDuration.toFixed(2),
|
|
697
|
+
isVisible: () => true,
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
key: "maxDuration",
|
|
701
|
+
label: "Max (ms)",
|
|
702
|
+
format: (data) => data.maxDuration.toFixed(2),
|
|
703
|
+
isVisible: () => true,
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
key: "stdDev",
|
|
707
|
+
label: "Std Dev (ms)",
|
|
708
|
+
format: (data) => data.stdDev.toFixed(2),
|
|
709
|
+
isVisible: () => true,
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
key: "median",
|
|
713
|
+
label: "Median (ms)",
|
|
714
|
+
format: (data) => data.median.toFixed(2),
|
|
715
|
+
isVisible: () => true,
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
key: "p95",
|
|
719
|
+
label: "P95 (ms)",
|
|
720
|
+
format: (data) => data.p95.toFixed(2),
|
|
721
|
+
isVisible: () => true,
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
key: "p99",
|
|
725
|
+
label: "P99 (ms)",
|
|
726
|
+
format: (data) => data.p99.toFixed(2),
|
|
727
|
+
isVisible: () => true,
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
key: "avgWaitTime",
|
|
731
|
+
label: "Avg Wait (ms)",
|
|
732
|
+
format: (data) => data.avgWaitTime.toFixed(2),
|
|
733
|
+
isVisible: () => true,
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
key: "minWaitTime",
|
|
737
|
+
label: "Min Wait (ms)",
|
|
738
|
+
format: (data) => data.minWaitTime.toFixed(2),
|
|
739
|
+
isVisible: () => true,
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
key: "maxWaitTime",
|
|
743
|
+
label: "Max Wait (ms)",
|
|
744
|
+
format: (data) => data.maxWaitTime.toFixed(2),
|
|
745
|
+
isVisible: () => true,
|
|
746
|
+
},
|
|
747
|
+
];
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Column configuration for risk management markdown reports.
|
|
751
|
+
*
|
|
752
|
+
* Defines the table structure for displaying risk rejection events in risk management reports.
|
|
753
|
+
* Each column specifies how to format and display signals that were rejected due to risk limits.
|
|
754
|
+
*
|
|
755
|
+
* Used by {@link RiskMarkdownService} to generate markdown tables showing:
|
|
756
|
+
* - Signal identification (symbol, strategy name, signal ID, position)
|
|
757
|
+
* - Exchange information (exchange name, active position count)
|
|
758
|
+
* - Price data (open price, take profit, stop loss, current price)
|
|
759
|
+
* - Rejection details (rejection reason/comment, timestamp)
|
|
760
|
+
*
|
|
761
|
+
* @remarks
|
|
762
|
+
* This configuration helps analyze when and why the risk management system rejected signals.
|
|
763
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
764
|
+
* Useful for tuning risk parameters and understanding risk control effectiveness.
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* ```typescript
|
|
768
|
+
* import { risk_columns } from "./assets/risk.columns";
|
|
769
|
+
*
|
|
770
|
+
* // Use with RiskMarkdownService
|
|
771
|
+
* const service = new RiskMarkdownService();
|
|
772
|
+
* await service.getReport("BTCUSDT", "my-strategy", risk_columns);
|
|
773
|
+
*
|
|
774
|
+
* // Or customize to focus on rejection reasons
|
|
775
|
+
* const customColumns = risk_columns.filter(col =>
|
|
776
|
+
* ["symbol", "strategyName", "comment", "activePositionCount", "timestamp"].includes(col.key)
|
|
777
|
+
* );
|
|
778
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
779
|
+
* ```
|
|
780
|
+
*
|
|
781
|
+
* @see {@link RiskMarkdownService} for usage in report generation
|
|
782
|
+
* @see {@link ColumnModel} for column interface definition
|
|
783
|
+
* @see {@link RiskEvent} for data structure
|
|
784
|
+
*/
|
|
785
|
+
const risk_columns = [
|
|
786
|
+
{
|
|
787
|
+
key: "symbol",
|
|
788
|
+
label: "Symbol",
|
|
789
|
+
format: (data) => data.symbol,
|
|
790
|
+
isVisible: () => true,
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
key: "strategyName",
|
|
794
|
+
label: "Strategy",
|
|
795
|
+
format: (data) => data.strategyName,
|
|
796
|
+
isVisible: () => true,
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
key: "signalId",
|
|
800
|
+
label: "Signal ID",
|
|
801
|
+
format: (data) => data.pendingSignal.id || "N/A",
|
|
802
|
+
isVisible: () => true,
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
key: "position",
|
|
806
|
+
label: "Position",
|
|
807
|
+
format: (data) => data.pendingSignal.position.toUpperCase(),
|
|
808
|
+
isVisible: () => true,
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
key: "note",
|
|
812
|
+
label: "Note",
|
|
813
|
+
format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
|
|
814
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
key: "exchangeName",
|
|
818
|
+
label: "Exchange",
|
|
819
|
+
format: (data) => data.exchangeName,
|
|
820
|
+
isVisible: () => true,
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
key: "openPrice",
|
|
824
|
+
label: "Open Price",
|
|
825
|
+
format: (data) => data.pendingSignal.priceOpen !== undefined
|
|
826
|
+
? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
|
|
827
|
+
: "N/A",
|
|
828
|
+
isVisible: () => true,
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
key: "takeProfit",
|
|
832
|
+
label: "Take Profit",
|
|
833
|
+
format: (data) => data.pendingSignal.priceTakeProfit !== undefined
|
|
834
|
+
? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
|
|
835
|
+
: "N/A",
|
|
836
|
+
isVisible: () => true,
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
key: "stopLoss",
|
|
840
|
+
label: "Stop Loss",
|
|
841
|
+
format: (data) => data.pendingSignal.priceStopLoss !== undefined
|
|
842
|
+
? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
|
|
843
|
+
: "N/A",
|
|
844
|
+
isVisible: () => true,
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
key: "currentPrice",
|
|
848
|
+
label: "Current Price",
|
|
849
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
850
|
+
isVisible: () => true,
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
key: "activePositionCount",
|
|
854
|
+
label: "Active Positions",
|
|
855
|
+
format: (data) => data.activePositionCount.toString(),
|
|
856
|
+
isVisible: () => true,
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
key: "comment",
|
|
860
|
+
label: "Reason",
|
|
861
|
+
format: (data) => data.comment,
|
|
862
|
+
isVisible: () => true,
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
key: "timestamp",
|
|
866
|
+
label: "Timestamp",
|
|
867
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
868
|
+
isVisible: () => true,
|
|
869
|
+
},
|
|
870
|
+
];
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Column configuration for scheduled signals markdown reports.
|
|
874
|
+
*
|
|
875
|
+
* Defines the table structure for displaying scheduled, opened, and cancelled signal events.
|
|
876
|
+
* Each column specifies how to format and display signal scheduling and activation data.
|
|
877
|
+
*
|
|
878
|
+
* Used by {@link ScheduleMarkdownService} to generate markdown tables showing:
|
|
879
|
+
* - Event information (timestamp, action: scheduled/opened/cancelled)
|
|
880
|
+
* - Signal identification (symbol, signal ID, position)
|
|
881
|
+
* - Price data (current price, entry price, take profit, stop loss)
|
|
882
|
+
* - Timing information (wait time in minutes before activation or cancellation)
|
|
883
|
+
*
|
|
884
|
+
* @remarks
|
|
885
|
+
* This configuration tracks the lifecycle of scheduled signals from creation to activation or cancellation.
|
|
886
|
+
* The "note" column visibility is controlled by {@link GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE}.
|
|
887
|
+
* Helps analyze signal scheduling effectiveness and cancellation patterns.
|
|
888
|
+
*
|
|
889
|
+
* @example
|
|
890
|
+
* ```typescript
|
|
891
|
+
* import { schedule_columns } from "./assets/schedule.columns";
|
|
892
|
+
*
|
|
893
|
+
* // Use with ScheduleMarkdownService
|
|
894
|
+
* const service = new ScheduleMarkdownService();
|
|
895
|
+
* await service.getReport("BTCUSDT", "my-strategy", schedule_columns);
|
|
896
|
+
*
|
|
897
|
+
* // Or customize for timing analysis
|
|
898
|
+
* const customColumns = schedule_columns.filter(col =>
|
|
899
|
+
* ["timestamp", "action", "symbol", "duration"].includes(col.key)
|
|
900
|
+
* );
|
|
901
|
+
* await service.getReport("BTCUSDT", "my-strategy", customColumns);
|
|
902
|
+
* ```
|
|
903
|
+
*
|
|
904
|
+
* @see {@link ScheduleMarkdownService} for usage in report generation
|
|
905
|
+
* @see {@link ColumnModel} for column interface definition
|
|
906
|
+
* @see {@link ScheduledEvent} for data structure
|
|
907
|
+
*/
|
|
908
|
+
const schedule_columns = [
|
|
909
|
+
{
|
|
910
|
+
key: "timestamp",
|
|
911
|
+
label: "Timestamp",
|
|
912
|
+
format: (data) => new Date(data.timestamp).toISOString(),
|
|
913
|
+
isVisible: () => true,
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
key: "action",
|
|
917
|
+
label: "Action",
|
|
918
|
+
format: (data) => data.action.toUpperCase(),
|
|
919
|
+
isVisible: () => true,
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
key: "symbol",
|
|
923
|
+
label: "Symbol",
|
|
924
|
+
format: (data) => data.symbol,
|
|
925
|
+
isVisible: () => true,
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
key: "signalId",
|
|
929
|
+
label: "Signal ID",
|
|
930
|
+
format: (data) => data.signalId,
|
|
931
|
+
isVisible: () => true,
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
key: "position",
|
|
935
|
+
label: "Position",
|
|
936
|
+
format: (data) => data.position.toUpperCase(),
|
|
937
|
+
isVisible: () => true,
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
key: "note",
|
|
941
|
+
label: "Note",
|
|
942
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
943
|
+
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
key: "currentPrice",
|
|
947
|
+
label: "Current Price",
|
|
948
|
+
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
949
|
+
isVisible: () => true,
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
key: "priceOpen",
|
|
953
|
+
label: "Entry Price",
|
|
954
|
+
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
955
|
+
isVisible: () => true,
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
key: "takeProfit",
|
|
959
|
+
label: "Take Profit",
|
|
960
|
+
format: (data) => `${data.takeProfit.toFixed(8)} USD`,
|
|
961
|
+
isVisible: () => true,
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
key: "stopLoss",
|
|
965
|
+
label: "Stop Loss",
|
|
966
|
+
format: (data) => `${data.stopLoss.toFixed(8)} USD`,
|
|
967
|
+
isVisible: () => true,
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
key: "duration",
|
|
971
|
+
label: "Wait Time (min)",
|
|
972
|
+
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
973
|
+
isVisible: () => true,
|
|
974
|
+
},
|
|
975
|
+
];
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Column configuration for walker strategy comparison table in markdown reports.
|
|
979
|
+
*
|
|
980
|
+
* Defines the table structure for displaying strategy comparison results in walker backtest reports.
|
|
981
|
+
* Each column specifies how to format and display aggregated strategy performance metrics.
|
|
982
|
+
*
|
|
983
|
+
* Used by {@link WalkerMarkdownService} to generate markdown tables showing:
|
|
984
|
+
* - Strategy ranking and identification
|
|
985
|
+
* - Optimization metric value (generic "Metric" column)
|
|
986
|
+
* - Performance statistics (Total Signals, Win Rate, Average PNL, Total PNL)
|
|
987
|
+
* - Risk metrics (Sharpe Ratio, Standard Deviation)
|
|
988
|
+
*
|
|
989
|
+
* @remarks
|
|
990
|
+
* This configuration is used in walker reports to compare multiple strategy configurations.
|
|
991
|
+
* The "Metric" column displays the value of the metric being optimized (Sharpe, PNL, etc.).
|
|
992
|
+
* Strategies are automatically sorted by metric value (best performers first).
|
|
993
|
+
*
|
|
994
|
+
* @example
|
|
995
|
+
* ```typescript
|
|
996
|
+
* import { walker_strategy_columns } from "./assets/walker.columns";
|
|
997
|
+
*
|
|
998
|
+
* // Use with WalkerMarkdownService for strategy comparison
|
|
999
|
+
* const service = new WalkerMarkdownService();
|
|
1000
|
+
* await service.getReport(
|
|
1001
|
+
* "my-walker",
|
|
1002
|
+
* "BTCUSDT",
|
|
1003
|
+
* "sharpeRatio",
|
|
1004
|
+
* { exchangeName: "binance", frameName: "1d" },
|
|
1005
|
+
* walker_strategy_columns
|
|
1006
|
+
* );
|
|
1007
|
+
* ```
|
|
1008
|
+
*
|
|
1009
|
+
* @see {@link WalkerMarkdownService} for usage in report generation
|
|
1010
|
+
* @see {@link ColumnModel} for column interface definition
|
|
1011
|
+
* @see {@link IStrategyResult} for data structure
|
|
1012
|
+
*/
|
|
1013
|
+
const walker_strategy_columns = [
|
|
1014
|
+
{
|
|
1015
|
+
key: "rank",
|
|
1016
|
+
label: "Rank",
|
|
1017
|
+
format: (_data, index) => `${index + 1}`,
|
|
1018
|
+
isVisible: () => true,
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
key: "strategy",
|
|
1022
|
+
label: "Strategy",
|
|
1023
|
+
format: (data) => data.strategyName,
|
|
1024
|
+
isVisible: () => true,
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
key: "metric",
|
|
1028
|
+
label: "Metric",
|
|
1029
|
+
format: (data) => data.metricValue !== null ? data.metricValue.toFixed(2) : "N/A",
|
|
1030
|
+
isVisible: () => true,
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
key: "totalSignals",
|
|
1034
|
+
label: "Total Signals",
|
|
1035
|
+
format: (data) => `${data.stats.totalSignals}`,
|
|
1036
|
+
isVisible: () => true,
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
key: "winRate",
|
|
1040
|
+
label: "Win Rate",
|
|
1041
|
+
format: (data) => data.stats.winRate !== null
|
|
1042
|
+
? `${data.stats.winRate.toFixed(2)}%`
|
|
1043
|
+
: "N/A",
|
|
1044
|
+
isVisible: () => true,
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
key: "avgPnl",
|
|
1048
|
+
label: "Avg PNL",
|
|
1049
|
+
format: (data) => data.stats.avgPnl !== null
|
|
1050
|
+
? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
|
|
1051
|
+
: "N/A",
|
|
1052
|
+
isVisible: () => true,
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
key: "totalPnl",
|
|
1056
|
+
label: "Total PNL",
|
|
1057
|
+
format: (data) => data.stats.totalPnl !== null
|
|
1058
|
+
? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
|
|
1059
|
+
: "N/A",
|
|
1060
|
+
isVisible: () => true,
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
key: "sharpeRatio",
|
|
1064
|
+
label: "Sharpe Ratio",
|
|
1065
|
+
format: (data) => data.stats.sharpeRatio !== null
|
|
1066
|
+
? `${data.stats.sharpeRatio.toFixed(3)}`
|
|
1067
|
+
: "N/A",
|
|
1068
|
+
isVisible: () => true,
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
key: "stdDev",
|
|
1072
|
+
label: "Std Dev",
|
|
1073
|
+
format: (data) => data.stats.stdDev !== null
|
|
1074
|
+
? `${data.stats.stdDev.toFixed(3)}%`
|
|
1075
|
+
: "N/A",
|
|
1076
|
+
isVisible: () => true,
|
|
1077
|
+
},
|
|
1078
|
+
];
|
|
1079
|
+
/**
|
|
1080
|
+
* Column configuration for walker PNL table in markdown reports.
|
|
1081
|
+
*
|
|
1082
|
+
* Defines the table structure for displaying all closed signals across all strategies in walker backtest reports.
|
|
1083
|
+
* Each column specifies how to format and display individual signal trade data.
|
|
1084
|
+
*
|
|
1085
|
+
* Used by {@link WalkerMarkdownService} to generate markdown tables showing:
|
|
1086
|
+
* - Strategy identification for each signal
|
|
1087
|
+
* - Signal details (signal ID, symbol, position)
|
|
1088
|
+
* - Trade performance (PNL percentage, close reason)
|
|
1089
|
+
* - Timing information (open time, close time)
|
|
1090
|
+
*
|
|
1091
|
+
* @remarks
|
|
1092
|
+
* This configuration aggregates all signals from all tested strategies into a single comprehensive table.
|
|
1093
|
+
* Useful for detailed analysis of individual trades across different strategy configurations.
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```typescript
|
|
1097
|
+
* import { walker_pnl_columns } from "./assets/walker.columns";
|
|
1098
|
+
*
|
|
1099
|
+
* // Use with WalkerMarkdownService for signal-level analysis
|
|
1100
|
+
* const service = new WalkerMarkdownService();
|
|
1101
|
+
* await service.getReport(
|
|
1102
|
+
* "my-walker",
|
|
1103
|
+
* "BTCUSDT",
|
|
1104
|
+
* "sharpeRatio",
|
|
1105
|
+
* { exchangeName: "binance", frameName: "1d" },
|
|
1106
|
+
* undefined, // use default strategy columns
|
|
1107
|
+
* walker_pnl_columns
|
|
1108
|
+
* );
|
|
1109
|
+
* ```
|
|
1110
|
+
*
|
|
1111
|
+
* @see {@link WalkerMarkdownService} for usage in report generation
|
|
1112
|
+
* @see {@link ColumnModel} for column interface definition
|
|
1113
|
+
* @see {@link SignalData} for data structure
|
|
1114
|
+
*/
|
|
1115
|
+
const walker_pnl_columns = [
|
|
1116
|
+
{
|
|
1117
|
+
key: "strategy",
|
|
1118
|
+
label: "Strategy",
|
|
1119
|
+
format: (data) => data.strategyName,
|
|
1120
|
+
isVisible: () => true,
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
key: "signalId",
|
|
1124
|
+
label: "Signal ID",
|
|
1125
|
+
format: (data) => data.signalId,
|
|
1126
|
+
isVisible: () => true,
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
key: "symbol",
|
|
1130
|
+
label: "Symbol",
|
|
1131
|
+
format: (data) => data.symbol,
|
|
1132
|
+
isVisible: () => true,
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
key: "position",
|
|
1136
|
+
label: "Position",
|
|
1137
|
+
format: (data) => data.position.toUpperCase(),
|
|
1138
|
+
isVisible: () => true,
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
key: "pnl",
|
|
1142
|
+
label: "PNL (net)",
|
|
1143
|
+
format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
|
|
1144
|
+
isVisible: () => true,
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
key: "closeReason",
|
|
1148
|
+
label: "Close Reason",
|
|
1149
|
+
format: (data) => data.closeReason,
|
|
1150
|
+
isVisible: () => true,
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
key: "openTime",
|
|
1154
|
+
label: "Open Time",
|
|
1155
|
+
format: (data) => new Date(data.openTime).toISOString(),
|
|
1156
|
+
isVisible: () => true,
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
key: "closeTime",
|
|
1160
|
+
label: "Close Time",
|
|
1161
|
+
format: (data) => new Date(data.closeTime).toISOString(),
|
|
1162
|
+
isVisible: () => true,
|
|
1163
|
+
},
|
|
1164
|
+
];
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Mapping of available table/markdown reports to their column definitions.
|
|
1168
|
+
*
|
|
1169
|
+
* Each property references a column definition object imported from
|
|
1170
|
+
* `src/assets/*.columns`. These are used by markdown/report generators
|
|
1171
|
+
* (backtest, live, schedule, risk, heat, performance, partial, walker).
|
|
1172
|
+
*/
|
|
1173
|
+
const COLUMN_CONFIG = {
|
|
1174
|
+
/** Columns used in backtest markdown tables and reports */
|
|
1175
|
+
backtest_columns,
|
|
1176
|
+
/** Columns used by heatmap / heat reports */
|
|
1177
|
+
heat_columns,
|
|
1178
|
+
/** Columns for live trading reports and logs */
|
|
1179
|
+
live_columns,
|
|
1180
|
+
/** Columns for partial-results / incremental reports */
|
|
1181
|
+
partial_columns,
|
|
1182
|
+
/** Columns for performance summary reports */
|
|
1183
|
+
performance_columns,
|
|
1184
|
+
/** Columns for risk-related reports */
|
|
1185
|
+
risk_columns,
|
|
1186
|
+
/** Columns for scheduled report output */
|
|
1187
|
+
schedule_columns,
|
|
1188
|
+
/** Walker: PnL summary columns */
|
|
1189
|
+
walker_pnl_columns,
|
|
1190
|
+
/** Walker: strategy-level summary columns */
|
|
1191
|
+
walker_strategy_columns,
|
|
1192
|
+
};
|
|
1193
|
+
/**
|
|
1194
|
+
* Immutable default columns mapping used across the application.
|
|
1195
|
+
* Use `DEFAULT_COLUMNS` when you need a read-only reference to the
|
|
1196
|
+
* canonical column configuration.
|
|
1197
|
+
*/
|
|
1198
|
+
const DEFAULT_COLUMNS = Object.freeze({ ...COLUMN_CONFIG });
|
|
1199
|
+
|
|
125
1200
|
const { init, inject, provide } = diKit.createActivator("backtest");
|
|
126
1201
|
|
|
127
1202
|
/**
|
|
@@ -221,6 +1296,7 @@ const validationServices$1 = {
|
|
|
221
1296
|
riskValidationService: Symbol('riskValidationService'),
|
|
222
1297
|
optimizerValidationService: Symbol('optimizerValidationService'),
|
|
223
1298
|
configValidationService: Symbol('configValidationService'),
|
|
1299
|
+
columnValidationService: Symbol('columnValidationService'),
|
|
224
1300
|
};
|
|
225
1301
|
const templateServices$1 = {
|
|
226
1302
|
optimizerTemplateService: Symbol('optimizerTemplateService'),
|
|
@@ -1552,7 +2628,7 @@ const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
|
1552
2628
|
* Utility class for managing partial profit/loss levels persistence.
|
|
1553
2629
|
*
|
|
1554
2630
|
* Features:
|
|
1555
|
-
* - Memoized storage instances per symbol
|
|
2631
|
+
* - Memoized storage instances per symbol:strategyName
|
|
1556
2632
|
* - Custom adapter support
|
|
1557
2633
|
* - Atomic read/write operations for partial data
|
|
1558
2634
|
* - Crash-safe partial state management
|
|
@@ -1562,23 +2638,25 @@ const PersistScheduleAdapter = new PersistScheduleUtils();
|
|
|
1562
2638
|
class PersistPartialUtils {
|
|
1563
2639
|
constructor() {
|
|
1564
2640
|
this.PersistPartialFactory = PersistBase;
|
|
1565
|
-
this.getPartialStorage = functoolsKit.memoize(([symbol]) => `${symbol}`, (symbol) => Reflect.construct(this.PersistPartialFactory, [
|
|
1566
|
-
symbol
|
|
2641
|
+
this.getPartialStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => Reflect.construct(this.PersistPartialFactory, [
|
|
2642
|
+
`${symbol}_${strategyName}`,
|
|
1567
2643
|
`./dump/data/partial/`,
|
|
1568
2644
|
]));
|
|
1569
2645
|
/**
|
|
1570
|
-
* Reads persisted partial data for a symbol.
|
|
2646
|
+
* Reads persisted partial data for a symbol and strategy.
|
|
1571
2647
|
*
|
|
1572
2648
|
* Called by ClientPartial.waitForInit() to restore state.
|
|
1573
2649
|
* Returns empty object if no partial data exists.
|
|
1574
2650
|
*
|
|
1575
2651
|
* @param symbol - Trading pair symbol
|
|
2652
|
+
* @param strategyName - Strategy identifier
|
|
1576
2653
|
* @returns Promise resolving to partial data record
|
|
1577
2654
|
*/
|
|
1578
|
-
this.readPartialData = async (symbol) => {
|
|
2655
|
+
this.readPartialData = async (symbol, strategyName) => {
|
|
1579
2656
|
backtest$1.loggerService.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_READ_DATA);
|
|
1580
|
-
const
|
|
1581
|
-
const
|
|
2657
|
+
const key = `${symbol}:${strategyName}`;
|
|
2658
|
+
const isInitial = !this.getPartialStorage.has(key);
|
|
2659
|
+
const stateStorage = this.getPartialStorage(symbol, strategyName);
|
|
1582
2660
|
await stateStorage.waitForInit(isInitial);
|
|
1583
2661
|
const PARTIAL_STORAGE_KEY = "levels";
|
|
1584
2662
|
if (await stateStorage.hasValue(PARTIAL_STORAGE_KEY)) {
|
|
@@ -1594,12 +2672,14 @@ class PersistPartialUtils {
|
|
|
1594
2672
|
*
|
|
1595
2673
|
* @param partialData - Record of signal IDs to partial data
|
|
1596
2674
|
* @param symbol - Trading pair symbol
|
|
2675
|
+
* @param strategyName - Strategy identifier
|
|
1597
2676
|
* @returns Promise that resolves when write is complete
|
|
1598
2677
|
*/
|
|
1599
|
-
this.writePartialData = async (partialData, symbol) => {
|
|
2678
|
+
this.writePartialData = async (partialData, symbol, strategyName) => {
|
|
1600
2679
|
backtest$1.loggerService.info(PERSIST_PARTIAL_UTILS_METHOD_NAME_WRITE_DATA);
|
|
1601
|
-
const
|
|
1602
|
-
const
|
|
2680
|
+
const key = `${symbol}:${strategyName}`;
|
|
2681
|
+
const isInitial = !this.getPartialStorage.has(key);
|
|
2682
|
+
const stateStorage = this.getPartialStorage(symbol, strategyName);
|
|
1603
2683
|
await stateStorage.waitForInit(isInitial);
|
|
1604
2684
|
const PARTIAL_STORAGE_KEY = "levels";
|
|
1605
2685
|
await stateStorage.writeValue(PARTIAL_STORAGE_KEY, partialData);
|
|
@@ -1634,10 +2714,10 @@ class PersistPartialUtils {
|
|
|
1634
2714
|
* PersistPartialAdapter.usePersistPartialAdapter(RedisPersist);
|
|
1635
2715
|
*
|
|
1636
2716
|
* // Read partial data
|
|
1637
|
-
* const partialData = await PersistPartialAdapter.readPartialData("BTCUSDT");
|
|
2717
|
+
* const partialData = await PersistPartialAdapter.readPartialData("BTCUSDT", "my-strategy");
|
|
1638
2718
|
*
|
|
1639
2719
|
* // Write partial data
|
|
1640
|
-
* await PersistPartialAdapter.writePartialData(partialData, "BTCUSDT");
|
|
2720
|
+
* await PersistPartialAdapter.writePartialData(partialData, "BTCUSDT", "my-strategy");
|
|
1641
2721
|
* ```
|
|
1642
2722
|
*/
|
|
1643
2723
|
const PersistPartialAdapter = new PersistPartialUtils();
|
|
@@ -1765,53 +2845,6 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
1765
2845
|
walkerStopSubject: walkerStopSubject
|
|
1766
2846
|
});
|
|
1767
2847
|
|
|
1768
|
-
/**
|
|
1769
|
-
* Converts markdown content to plain text with minimal formatting
|
|
1770
|
-
* @param content - Markdown string to convert
|
|
1771
|
-
* @returns Plain text representation
|
|
1772
|
-
*/
|
|
1773
|
-
const toPlainString = (content) => {
|
|
1774
|
-
if (!content) {
|
|
1775
|
-
return "";
|
|
1776
|
-
}
|
|
1777
|
-
let text = content;
|
|
1778
|
-
// Remove code blocks
|
|
1779
|
-
text = text.replace(/```[\s\S]*?```/g, "");
|
|
1780
|
-
text = text.replace(/`([^`]+)`/g, "$1");
|
|
1781
|
-
// Remove images
|
|
1782
|
-
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
1783
|
-
// Convert links to text only (keep link text, remove URL)
|
|
1784
|
-
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
1785
|
-
// Remove headers (convert to plain text)
|
|
1786
|
-
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
1787
|
-
// Remove bold and italic markers
|
|
1788
|
-
text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
|
|
1789
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "$1");
|
|
1790
|
-
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
1791
|
-
text = text.replace(/___(.+?)___/g, "$1");
|
|
1792
|
-
text = text.replace(/__(.+?)__/g, "$1");
|
|
1793
|
-
text = text.replace(/_(.+?)_/g, "$1");
|
|
1794
|
-
// Remove strikethrough
|
|
1795
|
-
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
1796
|
-
// Convert lists to plain text with bullets
|
|
1797
|
-
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
1798
|
-
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
1799
|
-
// Remove blockquotes
|
|
1800
|
-
text = text.replace(/^\s*>\s+/gm, "");
|
|
1801
|
-
// Remove horizontal rules
|
|
1802
|
-
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
1803
|
-
// Remove HTML tags
|
|
1804
|
-
text = text.replace(/<[^>]+>/g, "");
|
|
1805
|
-
// Remove excessive whitespace and normalize line breaks
|
|
1806
|
-
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
1807
|
-
text = text.replace(/[ \t]+/g, " ");
|
|
1808
|
-
// Remove all newline characters
|
|
1809
|
-
text = text.replace(/\n/g, " ");
|
|
1810
|
-
// Remove excessive spaces after newline removal
|
|
1811
|
-
text = text.replace(/\s+/g, " ");
|
|
1812
|
-
return text.trim();
|
|
1813
|
-
};
|
|
1814
|
-
|
|
1815
2848
|
const INTERVAL_MINUTES$1 = {
|
|
1816
2849
|
"1m": 1,
|
|
1817
2850
|
"3m": 3,
|
|
@@ -3390,7 +4423,7 @@ class RiskUtils {
|
|
|
3390
4423
|
*
|
|
3391
4424
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3392
4425
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
3393
|
-
* @returns Promise resolving to
|
|
4426
|
+
* @returns Promise resolving to RiskStatisticsModel object with counts and event list
|
|
3394
4427
|
*
|
|
3395
4428
|
* @example
|
|
3396
4429
|
* ```typescript
|
|
@@ -3437,6 +4470,7 @@ class RiskUtils {
|
|
|
3437
4470
|
*
|
|
3438
4471
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3439
4472
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
4473
|
+
* @param columns - Optional columns configuration for the report
|
|
3440
4474
|
* @returns Promise resolving to markdown formatted report string
|
|
3441
4475
|
*
|
|
3442
4476
|
* @example
|
|
@@ -3460,7 +4494,7 @@ class RiskUtils {
|
|
|
3460
4494
|
* // - my-strategy: 1
|
|
3461
4495
|
* ```
|
|
3462
4496
|
*/
|
|
3463
|
-
this.getReport = async (symbol, strategyName) => {
|
|
4497
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
3464
4498
|
backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, {
|
|
3465
4499
|
symbol,
|
|
3466
4500
|
strategyName,
|
|
@@ -3472,7 +4506,7 @@ class RiskUtils {
|
|
|
3472
4506
|
backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
|
|
3473
4507
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT));
|
|
3474
4508
|
}
|
|
3475
|
-
return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
|
|
4509
|
+
return await backtest$1.riskMarkdownService.getReport(symbol, strategyName, columns);
|
|
3476
4510
|
};
|
|
3477
4511
|
/**
|
|
3478
4512
|
* Generates and saves markdown report to file.
|
|
@@ -3489,6 +4523,7 @@ class RiskUtils {
|
|
|
3489
4523
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3490
4524
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
3491
4525
|
* @param path - Output directory path (default: "./dump/risk")
|
|
4526
|
+
* @param columns - Optional columns configuration for the report
|
|
3492
4527
|
* @returns Promise that resolves when file is written
|
|
3493
4528
|
*
|
|
3494
4529
|
* @example
|
|
@@ -3505,7 +4540,7 @@ class RiskUtils {
|
|
|
3505
4540
|
* }
|
|
3506
4541
|
* ```
|
|
3507
4542
|
*/
|
|
3508
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
4543
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
3509
4544
|
backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, {
|
|
3510
4545
|
symbol,
|
|
3511
4546
|
strategyName,
|
|
@@ -3518,7 +4553,7 @@ class RiskUtils {
|
|
|
3518
4553
|
backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
|
|
3519
4554
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP));
|
|
3520
4555
|
}
|
|
3521
|
-
await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
|
|
4556
|
+
await backtest$1.riskMarkdownService.dump(symbol, strategyName, path, columns);
|
|
3522
4557
|
};
|
|
3523
4558
|
}
|
|
3524
4559
|
}
|
|
@@ -3543,25 +4578,25 @@ const NOOP_RISK = {
|
|
|
3543
4578
|
addSignal: () => Promise.resolve(),
|
|
3544
4579
|
removeSignal: () => Promise.resolve(),
|
|
3545
4580
|
};
|
|
3546
|
-
const GET_RISK_FN = (dto, self) => {
|
|
4581
|
+
const GET_RISK_FN = (dto, backtest, self) => {
|
|
3547
4582
|
const hasRiskName = !!dto.riskName;
|
|
3548
|
-
const hasRiskList = !!
|
|
4583
|
+
const hasRiskList = !!dto.riskList?.length;
|
|
3549
4584
|
// Нет ни riskName, ни riskList
|
|
3550
4585
|
if (!hasRiskName && !hasRiskList) {
|
|
3551
4586
|
return NOOP_RISK;
|
|
3552
4587
|
}
|
|
3553
4588
|
// Есть только riskName (без riskList)
|
|
3554
4589
|
if (hasRiskName && !hasRiskList) {
|
|
3555
|
-
return self.riskConnectionService.getRisk(dto.riskName);
|
|
4590
|
+
return self.riskConnectionService.getRisk(dto.riskName, backtest);
|
|
3556
4591
|
}
|
|
3557
4592
|
// Есть только riskList (без riskName)
|
|
3558
4593
|
if (!hasRiskName && hasRiskList) {
|
|
3559
|
-
return new MergeRisk(dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName)));
|
|
4594
|
+
return new MergeRisk(dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName, backtest)));
|
|
3560
4595
|
}
|
|
3561
4596
|
// Есть и riskName, и riskList - объединяем (riskName в начало)
|
|
3562
4597
|
return new MergeRisk([
|
|
3563
|
-
self.riskConnectionService.getRisk(dto.riskName),
|
|
3564
|
-
...dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName))
|
|
4598
|
+
self.riskConnectionService.getRisk(dto.riskName, backtest),
|
|
4599
|
+
...dto.riskList.map((riskName) => self.riskConnectionService.getRisk(riskName, backtest)),
|
|
3565
4600
|
]);
|
|
3566
4601
|
};
|
|
3567
4602
|
/**
|
|
@@ -3603,7 +4638,7 @@ class StrategyConnectionService {
|
|
|
3603
4638
|
* @param strategyName - Name of registered strategy schema
|
|
3604
4639
|
* @returns Configured ClientStrategy instance
|
|
3605
4640
|
*/
|
|
3606
|
-
this.getStrategy = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, (symbol, strategyName) => {
|
|
4641
|
+
this.getStrategy = functoolsKit.memoize(([symbol, strategyName, backtest]) => `${symbol}:${strategyName}:${backtest ? "backtest" : "live"}`, (symbol, strategyName, backtest) => {
|
|
3607
4642
|
const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
|
|
3608
4643
|
return new ClientStrategy({
|
|
3609
4644
|
symbol,
|
|
@@ -3616,7 +4651,7 @@ class StrategyConnectionService {
|
|
|
3616
4651
|
risk: GET_RISK_FN({
|
|
3617
4652
|
riskName,
|
|
3618
4653
|
riskList,
|
|
3619
|
-
}, this),
|
|
4654
|
+
}, backtest, this),
|
|
3620
4655
|
riskName,
|
|
3621
4656
|
strategyName,
|
|
3622
4657
|
getSignal,
|
|
@@ -3633,12 +4668,13 @@ class StrategyConnectionService {
|
|
|
3633
4668
|
*
|
|
3634
4669
|
* @returns Promise resolving to pending signal or null
|
|
3635
4670
|
*/
|
|
3636
|
-
this.getPendingSignal = async (symbol, strategyName) => {
|
|
4671
|
+
this.getPendingSignal = async (backtest, symbol, strategyName) => {
|
|
3637
4672
|
this.loggerService.log("strategyConnectionService getPendingSignal", {
|
|
3638
4673
|
symbol,
|
|
3639
4674
|
strategyName,
|
|
4675
|
+
backtest,
|
|
3640
4676
|
});
|
|
3641
|
-
const strategy = this.getStrategy(symbol, strategyName);
|
|
4677
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3642
4678
|
return await strategy.getPendingSignal(symbol, strategyName);
|
|
3643
4679
|
};
|
|
3644
4680
|
/**
|
|
@@ -3651,12 +4687,13 @@ class StrategyConnectionService {
|
|
|
3651
4687
|
* @param strategyName - Name of the strategy
|
|
3652
4688
|
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
3653
4689
|
*/
|
|
3654
|
-
this.getStopped = async (symbol, strategyName) => {
|
|
4690
|
+
this.getStopped = async (backtest, symbol, strategyName) => {
|
|
3655
4691
|
this.loggerService.log("strategyConnectionService getStopped", {
|
|
3656
4692
|
symbol,
|
|
3657
4693
|
strategyName,
|
|
4694
|
+
backtest,
|
|
3658
4695
|
});
|
|
3659
|
-
const strategy = this.getStrategy(symbol, strategyName);
|
|
4696
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3660
4697
|
return await strategy.getStopped(symbol, strategyName);
|
|
3661
4698
|
};
|
|
3662
4699
|
/**
|
|
@@ -3674,7 +4711,8 @@ class StrategyConnectionService {
|
|
|
3674
4711
|
symbol,
|
|
3675
4712
|
strategyName,
|
|
3676
4713
|
});
|
|
3677
|
-
const
|
|
4714
|
+
const backtest = this.executionContextService.context.backtest;
|
|
4715
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3678
4716
|
await strategy.waitForInit();
|
|
3679
4717
|
const tick = await strategy.tick(symbol, strategyName);
|
|
3680
4718
|
{
|
|
@@ -3705,7 +4743,8 @@ class StrategyConnectionService {
|
|
|
3705
4743
|
strategyName,
|
|
3706
4744
|
candleCount: candles.length,
|
|
3707
4745
|
});
|
|
3708
|
-
const
|
|
4746
|
+
const backtest = this.executionContextService.context.backtest;
|
|
4747
|
+
const strategy = this.getStrategy(symbol, strategyName, backtest);
|
|
3709
4748
|
await strategy.waitForInit();
|
|
3710
4749
|
const tick = await strategy.backtest(symbol, strategyName, candles);
|
|
3711
4750
|
{
|
|
@@ -3726,11 +4765,11 @@ class StrategyConnectionService {
|
|
|
3726
4765
|
* @param strategyName - Name of strategy to stop
|
|
3727
4766
|
* @returns Promise that resolves when stop flag is set
|
|
3728
4767
|
*/
|
|
3729
|
-
this.stop = async (
|
|
4768
|
+
this.stop = async (backtest, ctx) => {
|
|
3730
4769
|
this.loggerService.log("strategyConnectionService stop", {
|
|
3731
4770
|
ctx,
|
|
3732
4771
|
});
|
|
3733
|
-
const strategy = this.getStrategy(ctx.symbol, ctx.strategyName);
|
|
4772
|
+
const strategy = this.getStrategy(ctx.symbol, ctx.strategyName, backtest);
|
|
3734
4773
|
await strategy.stop(ctx.symbol, ctx.strategyName, backtest);
|
|
3735
4774
|
};
|
|
3736
4775
|
/**
|
|
@@ -3741,12 +4780,12 @@ class StrategyConnectionService {
|
|
|
3741
4780
|
*
|
|
3742
4781
|
* @param ctx - Optional context with symbol and strategyName (clears all if not provided)
|
|
3743
4782
|
*/
|
|
3744
|
-
this.clear = async (ctx) => {
|
|
4783
|
+
this.clear = async (backtest, ctx) => {
|
|
3745
4784
|
this.loggerService.log("strategyConnectionService clear", {
|
|
3746
4785
|
ctx,
|
|
3747
4786
|
});
|
|
3748
4787
|
if (ctx) {
|
|
3749
|
-
const key = `${ctx.symbol}:${ctx.strategyName}`;
|
|
4788
|
+
const key = `${ctx.symbol}:${ctx.strategyName}:${backtest ? "backtest" : "live"}`;
|
|
3750
4789
|
this.getStrategy.clear(key);
|
|
3751
4790
|
}
|
|
3752
4791
|
else {
|
|
@@ -4141,9 +5180,15 @@ const DO_VALIDATION_FN = functoolsKit.trycatch(async (validation, params) => {
|
|
|
4141
5180
|
* Initializes active positions by reading from persistence.
|
|
4142
5181
|
* Uses singleshot pattern to ensure it only runs once.
|
|
4143
5182
|
* This function is exported for use in tests or other modules.
|
|
5183
|
+
*
|
|
5184
|
+
* In backtest mode, initializes with empty Map. In live mode, reads from persist storage.
|
|
4144
5185
|
*/
|
|
4145
5186
|
const WAIT_FOR_INIT_FN$1 = async (self) => {
|
|
4146
|
-
self.params.logger.debug("ClientRisk waitForInit");
|
|
5187
|
+
self.params.logger.debug("ClientRisk waitForInit", { backtest: self.params.backtest });
|
|
5188
|
+
if (self.params.backtest) {
|
|
5189
|
+
self._activePositions = new Map();
|
|
5190
|
+
return;
|
|
5191
|
+
}
|
|
4147
5192
|
const persistedPositions = await PersistRiskAdapter.readPositionData(self.params.riskName);
|
|
4148
5193
|
self._activePositions = new Map(persistedPositions);
|
|
4149
5194
|
};
|
|
@@ -4191,6 +5236,7 @@ class ClientRisk {
|
|
|
4191
5236
|
this.params.logger.debug("ClientRisk checkSignal", {
|
|
4192
5237
|
symbol: params.symbol,
|
|
4193
5238
|
strategyName: params.strategyName,
|
|
5239
|
+
backtest: this.params.backtest,
|
|
4194
5240
|
});
|
|
4195
5241
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4196
5242
|
await this.waitForInit();
|
|
@@ -4236,8 +5282,12 @@ class ClientRisk {
|
|
|
4236
5282
|
}
|
|
4237
5283
|
/**
|
|
4238
5284
|
* Persists current active positions to disk.
|
|
5285
|
+
* Skips in backtest mode.
|
|
4239
5286
|
*/
|
|
4240
5287
|
async _updatePositions() {
|
|
5288
|
+
if (this.params.backtest) {
|
|
5289
|
+
return;
|
|
5290
|
+
}
|
|
4241
5291
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4242
5292
|
await this.waitForInit();
|
|
4243
5293
|
}
|
|
@@ -4251,6 +5301,7 @@ class ClientRisk {
|
|
|
4251
5301
|
this.params.logger.debug("ClientRisk addSignal", {
|
|
4252
5302
|
symbol,
|
|
4253
5303
|
context,
|
|
5304
|
+
backtest: this.params.backtest,
|
|
4254
5305
|
});
|
|
4255
5306
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4256
5307
|
await this.waitForInit();
|
|
@@ -4273,6 +5324,7 @@ class ClientRisk {
|
|
|
4273
5324
|
this.params.logger.debug("ClientRisk removeSignal", {
|
|
4274
5325
|
symbol,
|
|
4275
5326
|
context,
|
|
5327
|
+
backtest: this.params.backtest,
|
|
4276
5328
|
});
|
|
4277
5329
|
if (this._activePositions === POSITION_NEED_FETCH) {
|
|
4278
5330
|
await this.waitForInit();
|
|
@@ -4343,19 +5395,21 @@ class RiskConnectionService {
|
|
|
4343
5395
|
this.loggerService = inject(TYPES.loggerService);
|
|
4344
5396
|
this.riskSchemaService = inject(TYPES.riskSchemaService);
|
|
4345
5397
|
/**
|
|
4346
|
-
* Retrieves memoized ClientRisk instance for given risk name.
|
|
5398
|
+
* Retrieves memoized ClientRisk instance for given risk name and backtest mode.
|
|
4347
5399
|
*
|
|
4348
5400
|
* Creates ClientRisk on first call, returns cached instance on subsequent calls.
|
|
4349
|
-
* Cache key is riskName string.
|
|
5401
|
+
* Cache key is "riskName:backtest" string to separate live and backtest instances.
|
|
4350
5402
|
*
|
|
4351
5403
|
* @param riskName - Name of registered risk schema
|
|
5404
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
4352
5405
|
* @returns Configured ClientRisk instance
|
|
4353
5406
|
*/
|
|
4354
|
-
this.getRisk = functoolsKit.memoize(([riskName]) => `${riskName}`, (riskName) => {
|
|
5407
|
+
this.getRisk = functoolsKit.memoize(([riskName, backtest]) => `${riskName}:${backtest ? "backtest" : "live"}`, (riskName, backtest) => {
|
|
4355
5408
|
const schema = this.riskSchemaService.get(riskName);
|
|
4356
5409
|
return new ClientRisk({
|
|
4357
5410
|
...schema,
|
|
4358
5411
|
logger: this.loggerService,
|
|
5412
|
+
backtest,
|
|
4359
5413
|
onRejected: COMMIT_REJECTION_FN,
|
|
4360
5414
|
});
|
|
4361
5415
|
});
|
|
@@ -4367,7 +5421,7 @@ class RiskConnectionService {
|
|
|
4367
5421
|
* ClientRisk will emit riskSubject event via onRejected callback when signal is rejected.
|
|
4368
5422
|
*
|
|
4369
5423
|
* @param params - Risk check arguments (portfolio state, position details)
|
|
4370
|
-
* @param context - Execution context with risk name
|
|
5424
|
+
* @param context - Execution context with risk name and backtest mode
|
|
4371
5425
|
* @returns Promise resolving to risk check result
|
|
4372
5426
|
*/
|
|
4373
5427
|
this.checkSignal = async (params, context) => {
|
|
@@ -4375,46 +5429,48 @@ class RiskConnectionService {
|
|
|
4375
5429
|
symbol: params.symbol,
|
|
4376
5430
|
context,
|
|
4377
5431
|
});
|
|
4378
|
-
return await this.getRisk(context.riskName).checkSignal(params);
|
|
5432
|
+
return await this.getRisk(context.riskName, context.backtest).checkSignal(params);
|
|
4379
5433
|
};
|
|
4380
5434
|
/**
|
|
4381
5435
|
* Registers an opened signal with the risk management system.
|
|
4382
5436
|
* Routes to appropriate ClientRisk instance.
|
|
4383
5437
|
*
|
|
4384
5438
|
* @param symbol - Trading pair symbol
|
|
4385
|
-
* @param context - Context information (strategyName, riskName)
|
|
5439
|
+
* @param context - Context information (strategyName, riskName, backtest)
|
|
4386
5440
|
*/
|
|
4387
5441
|
this.addSignal = async (symbol, context) => {
|
|
4388
5442
|
this.loggerService.log("riskConnectionService addSignal", {
|
|
4389
5443
|
symbol,
|
|
4390
5444
|
context,
|
|
4391
5445
|
});
|
|
4392
|
-
await this.getRisk(context.riskName).addSignal(symbol, context);
|
|
5446
|
+
await this.getRisk(context.riskName, context.backtest).addSignal(symbol, context);
|
|
4393
5447
|
};
|
|
4394
5448
|
/**
|
|
4395
5449
|
* Removes a closed signal from the risk management system.
|
|
4396
5450
|
* Routes to appropriate ClientRisk instance.
|
|
4397
5451
|
*
|
|
4398
5452
|
* @param symbol - Trading pair symbol
|
|
4399
|
-
* @param context - Context information (strategyName, riskName)
|
|
5453
|
+
* @param context - Context information (strategyName, riskName, backtest)
|
|
4400
5454
|
*/
|
|
4401
5455
|
this.removeSignal = async (symbol, context) => {
|
|
4402
5456
|
this.loggerService.log("riskConnectionService removeSignal", {
|
|
4403
5457
|
symbol,
|
|
4404
5458
|
context,
|
|
4405
5459
|
});
|
|
4406
|
-
await this.getRisk(context.riskName).removeSignal(symbol, context);
|
|
5460
|
+
await this.getRisk(context.riskName, context.backtest).removeSignal(symbol, context);
|
|
4407
5461
|
};
|
|
4408
5462
|
/**
|
|
4409
5463
|
* Clears the cached ClientRisk instance for the given risk name.
|
|
4410
5464
|
*
|
|
4411
5465
|
* @param riskName - Name of the risk schema to clear from cache
|
|
4412
5466
|
*/
|
|
4413
|
-
this.clear = async (riskName) => {
|
|
5467
|
+
this.clear = async (backtest, riskName) => {
|
|
4414
5468
|
this.loggerService.log("riskConnectionService clear", {
|
|
4415
5469
|
riskName,
|
|
5470
|
+
backtest,
|
|
4416
5471
|
});
|
|
4417
|
-
|
|
5472
|
+
const key = `${riskName}:${backtest ? "backtest" : "live"}`;
|
|
5473
|
+
this.getRisk.clear(key);
|
|
4418
5474
|
};
|
|
4419
5475
|
}
|
|
4420
5476
|
}
|
|
@@ -4637,7 +5693,7 @@ class StrategyCoreService {
|
|
|
4637
5693
|
* @param strategyName - Name of the strategy
|
|
4638
5694
|
* @returns Promise resolving to pending signal or null
|
|
4639
5695
|
*/
|
|
4640
|
-
this.getPendingSignal = async (symbol, strategyName) => {
|
|
5696
|
+
this.getPendingSignal = async (backtest, symbol, strategyName) => {
|
|
4641
5697
|
this.loggerService.log("strategyCoreService getPendingSignal", {
|
|
4642
5698
|
symbol,
|
|
4643
5699
|
strategyName,
|
|
@@ -4646,7 +5702,7 @@ class StrategyCoreService {
|
|
|
4646
5702
|
throw new Error("strategyCoreService getPendingSignal requires a method context");
|
|
4647
5703
|
}
|
|
4648
5704
|
await this.validate(symbol, strategyName);
|
|
4649
|
-
return await this.strategyConnectionService.getPendingSignal(symbol, strategyName);
|
|
5705
|
+
return await this.strategyConnectionService.getPendingSignal(backtest, symbol, strategyName);
|
|
4650
5706
|
};
|
|
4651
5707
|
/**
|
|
4652
5708
|
* Checks if the strategy has been stopped.
|
|
@@ -4658,16 +5714,17 @@ class StrategyCoreService {
|
|
|
4658
5714
|
* @param strategyName - Name of the strategy
|
|
4659
5715
|
* @returns Promise resolving to true if strategy is stopped, false otherwise
|
|
4660
5716
|
*/
|
|
4661
|
-
this.getStopped = async (symbol, strategyName) => {
|
|
5717
|
+
this.getStopped = async (backtest, symbol, strategyName) => {
|
|
4662
5718
|
this.loggerService.log("strategyCoreService getStopped", {
|
|
4663
5719
|
symbol,
|
|
4664
5720
|
strategyName,
|
|
5721
|
+
backtest,
|
|
4665
5722
|
});
|
|
4666
5723
|
if (!MethodContextService.hasContext()) {
|
|
4667
5724
|
throw new Error("strategyCoreService getStopped requires a method context");
|
|
4668
5725
|
}
|
|
4669
5726
|
await this.validate(symbol, strategyName);
|
|
4670
|
-
return await this.strategyConnectionService.getStopped(symbol, strategyName);
|
|
5727
|
+
return await this.strategyConnectionService.getStopped(backtest, symbol, strategyName);
|
|
4671
5728
|
};
|
|
4672
5729
|
/**
|
|
4673
5730
|
* Checks signal status at a specific timestamp.
|
|
@@ -4741,13 +5798,13 @@ class StrategyCoreService {
|
|
|
4741
5798
|
* @param strategyName - Name of strategy to stop
|
|
4742
5799
|
* @returns Promise that resolves when stop flag is set
|
|
4743
5800
|
*/
|
|
4744
|
-
this.stop = async (
|
|
5801
|
+
this.stop = async (backtest, ctx) => {
|
|
4745
5802
|
this.loggerService.log("strategyCoreService stop", {
|
|
4746
5803
|
ctx,
|
|
4747
5804
|
backtest,
|
|
4748
5805
|
});
|
|
4749
5806
|
await this.validate(ctx.symbol, ctx.strategyName);
|
|
4750
|
-
return await this.strategyConnectionService.stop(
|
|
5807
|
+
return await this.strategyConnectionService.stop(backtest, ctx);
|
|
4751
5808
|
};
|
|
4752
5809
|
/**
|
|
4753
5810
|
* Clears the memoized ClientStrategy instance from cache.
|
|
@@ -4757,14 +5814,14 @@ class StrategyCoreService {
|
|
|
4757
5814
|
*
|
|
4758
5815
|
* @param ctx - Optional context with symbol and strategyName (clears all if not provided)
|
|
4759
5816
|
*/
|
|
4760
|
-
this.clear = async (ctx) => {
|
|
5817
|
+
this.clear = async (backtest, ctx) => {
|
|
4761
5818
|
this.loggerService.log("strategyCoreService clear", {
|
|
4762
5819
|
ctx,
|
|
4763
5820
|
});
|
|
4764
5821
|
if (ctx) {
|
|
4765
5822
|
await this.validate(ctx.symbol, ctx.strategyName);
|
|
4766
5823
|
}
|
|
4767
|
-
return await this.strategyConnectionService.clear(ctx);
|
|
5824
|
+
return await this.strategyConnectionService.clear(backtest, ctx);
|
|
4768
5825
|
};
|
|
4769
5826
|
}
|
|
4770
5827
|
}
|
|
@@ -4905,14 +5962,15 @@ class RiskGlobalService {
|
|
|
4905
5962
|
* If no riskName is provided, clears all risk data.
|
|
4906
5963
|
* @param riskName - Optional name of the risk instance to clear
|
|
4907
5964
|
*/
|
|
4908
|
-
this.clear = async (riskName) => {
|
|
5965
|
+
this.clear = async (backtest, riskName) => {
|
|
4909
5966
|
this.loggerService.log("riskGlobalService clear", {
|
|
4910
5967
|
riskName,
|
|
5968
|
+
backtest,
|
|
4911
5969
|
});
|
|
4912
5970
|
if (riskName) {
|
|
4913
5971
|
await this.validate(riskName);
|
|
4914
5972
|
}
|
|
4915
|
-
return await this.riskConnectionService.clear(riskName);
|
|
5973
|
+
return await this.riskConnectionService.clear(backtest, riskName);
|
|
4916
5974
|
};
|
|
4917
5975
|
}
|
|
4918
5976
|
}
|
|
@@ -5035,6 +6093,19 @@ class StrategySchemaService {
|
|
|
5035
6093
|
if (typeof strategySchema.strategyName !== "string") {
|
|
5036
6094
|
throw new Error(`strategy schema validation failed: missing strategyName`);
|
|
5037
6095
|
}
|
|
6096
|
+
if (strategySchema.riskName && typeof strategySchema.riskName !== "string") {
|
|
6097
|
+
throw new Error(`strategy schema validation failed: invalid riskName`);
|
|
6098
|
+
}
|
|
6099
|
+
if (strategySchema.riskList && !Array.isArray(strategySchema.riskList)) {
|
|
6100
|
+
throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} system=${strategySchema.riskList}`);
|
|
6101
|
+
}
|
|
6102
|
+
if (strategySchema.riskList &&
|
|
6103
|
+
strategySchema.riskList.length !== new Set(strategySchema.riskList).size) {
|
|
6104
|
+
throw new Error(`strategy schema validation failed: found duplicate riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
|
|
6105
|
+
}
|
|
6106
|
+
if (strategySchema.riskList?.some((value) => typeof value !== "string")) {
|
|
6107
|
+
throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
|
|
6108
|
+
}
|
|
5038
6109
|
if (typeof strategySchema.interval !== "string") {
|
|
5039
6110
|
throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
|
|
5040
6111
|
}
|
|
@@ -5443,7 +6514,7 @@ class BacktestLogicPrivateService {
|
|
|
5443
6514
|
});
|
|
5444
6515
|
}
|
|
5445
6516
|
// Check if strategy should stop before processing next frame
|
|
5446
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6517
|
+
if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
|
|
5447
6518
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (before tick)", {
|
|
5448
6519
|
symbol,
|
|
5449
6520
|
when: when.toISOString(),
|
|
@@ -5468,7 +6539,7 @@ class BacktestLogicPrivateService {
|
|
|
5468
6539
|
continue;
|
|
5469
6540
|
}
|
|
5470
6541
|
// Check if strategy should stop when idle (no active signal)
|
|
5471
|
-
if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
6542
|
+
if (await functoolsKit.and(Promise.resolve(result.action === "idle"), this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName))) {
|
|
5472
6543
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (idle state)", {
|
|
5473
6544
|
symbol,
|
|
5474
6545
|
when: when.toISOString(),
|
|
@@ -5570,7 +6641,7 @@ class BacktestLogicPrivateService {
|
|
|
5570
6641
|
}
|
|
5571
6642
|
yield backtestResult;
|
|
5572
6643
|
// Check if strategy should stop after signal is closed
|
|
5573
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6644
|
+
if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
|
|
5574
6645
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after scheduled signal closed)", {
|
|
5575
6646
|
symbol,
|
|
5576
6647
|
signalId: backtestResult.signal.id,
|
|
@@ -5663,7 +6734,7 @@ class BacktestLogicPrivateService {
|
|
|
5663
6734
|
}
|
|
5664
6735
|
yield backtestResult;
|
|
5665
6736
|
// Check if strategy should stop after signal is closed
|
|
5666
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6737
|
+
if (await this.strategyCoreService.getStopped(true, symbol, this.methodContextService.context.strategyName)) {
|
|
5667
6738
|
this.loggerService.info("backtestLogicPrivateService stopped by user request (after signal closed)", {
|
|
5668
6739
|
symbol,
|
|
5669
6740
|
signalId: backtestResult.signal.id,
|
|
@@ -5806,7 +6877,7 @@ class LiveLogicPrivateService {
|
|
|
5806
6877
|
previousEventTimestamp = currentTimestamp;
|
|
5807
6878
|
// Check if strategy should stop when idle (no active signal)
|
|
5808
6879
|
if (result.action === "idle") {
|
|
5809
|
-
if (await functoolsKit.and(Promise.resolve(true), this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName))) {
|
|
6880
|
+
if (await functoolsKit.and(Promise.resolve(true), this.strategyCoreService.getStopped(false, symbol, this.methodContextService.context.strategyName))) {
|
|
5810
6881
|
this.loggerService.info("liveLogicPrivateService stopped by user request (idle state)", {
|
|
5811
6882
|
symbol,
|
|
5812
6883
|
when: when.toISOString(),
|
|
@@ -5828,7 +6899,7 @@ class LiveLogicPrivateService {
|
|
|
5828
6899
|
yield result;
|
|
5829
6900
|
// Check if strategy should stop after signal is closed
|
|
5830
6901
|
if (result.action === "closed") {
|
|
5831
|
-
if (await this.strategyCoreService.getStopped(symbol, this.methodContextService.context.strategyName)) {
|
|
6902
|
+
if (await this.strategyCoreService.getStopped(false, symbol, this.methodContextService.context.strategyName)) {
|
|
5832
6903
|
this.loggerService.info("liveLogicPrivateService stopped by user request (after signal closed)", {
|
|
5833
6904
|
symbol,
|
|
5834
6905
|
signalId: result.signal.id,
|
|
@@ -6276,167 +7347,80 @@ class BacktestCommandService {
|
|
|
6276
7347
|
}
|
|
6277
7348
|
{
|
|
6278
7349
|
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
6279
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
|
|
6280
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
|
|
6281
|
-
}
|
|
6282
|
-
return this.backtestLogicPublicService.run(symbol, context);
|
|
6283
|
-
};
|
|
6284
|
-
}
|
|
6285
|
-
}
|
|
6286
|
-
|
|
6287
|
-
const METHOD_NAME_RUN = "walkerCommandService run";
|
|
6288
|
-
/**
|
|
6289
|
-
* Global service providing access to walker functionality.
|
|
6290
|
-
*
|
|
6291
|
-
* Simple wrapper around WalkerLogicPublicService for dependency injection.
|
|
6292
|
-
* Used by public API exports.
|
|
6293
|
-
*/
|
|
6294
|
-
class WalkerCommandService {
|
|
6295
|
-
constructor() {
|
|
6296
|
-
this.loggerService = inject(TYPES.loggerService);
|
|
6297
|
-
this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
|
|
6298
|
-
this.walkerSchemaService = inject(TYPES.walkerSchemaService);
|
|
6299
|
-
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
6300
|
-
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
6301
|
-
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
6302
|
-
this.walkerValidationService = inject(TYPES.walkerValidationService);
|
|
6303
|
-
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
6304
|
-
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
6305
|
-
/**
|
|
6306
|
-
* Runs walker comparison for a symbol with context propagation.
|
|
6307
|
-
*
|
|
6308
|
-
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
6309
|
-
* @param context - Walker context with strategies and metric
|
|
6310
|
-
*/
|
|
6311
|
-
this.run = (symbol, context) => {
|
|
6312
|
-
this.loggerService.log(METHOD_NAME_RUN, {
|
|
6313
|
-
symbol,
|
|
6314
|
-
context,
|
|
6315
|
-
});
|
|
6316
|
-
{
|
|
6317
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
|
|
6318
|
-
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
|
|
6319
|
-
this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
|
|
6320
|
-
}
|
|
6321
|
-
{
|
|
6322
|
-
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
6323
|
-
for (const strategyName of walkerSchema.strategies) {
|
|
6324
|
-
const { riskName, riskList } = this.strategySchemaService.get(strategyName);
|
|
6325
|
-
this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
|
|
6326
|
-
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
|
|
6327
|
-
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
|
|
6328
|
-
}
|
|
6329
|
-
}
|
|
6330
|
-
return this.walkerLogicPublicService.run(symbol, context);
|
|
6331
|
-
};
|
|
6332
|
-
}
|
|
6333
|
-
}
|
|
6334
|
-
|
|
6335
|
-
/**
|
|
6336
|
-
* Checks if a value is unsafe for display (not a number, NaN, or Infinity).
|
|
6337
|
-
*
|
|
6338
|
-
* @param value - Value to check
|
|
6339
|
-
* @returns true if value is unsafe, false otherwise
|
|
6340
|
-
*/
|
|
6341
|
-
function isUnsafe$3(value) {
|
|
6342
|
-
if (typeof value !== "number") {
|
|
6343
|
-
return true;
|
|
6344
|
-
}
|
|
6345
|
-
if (isNaN(value)) {
|
|
6346
|
-
return true;
|
|
6347
|
-
}
|
|
6348
|
-
if (!isFinite(value)) {
|
|
6349
|
-
return true;
|
|
6350
|
-
}
|
|
6351
|
-
return false;
|
|
6352
|
-
}
|
|
6353
|
-
const columns$6 = [
|
|
6354
|
-
{
|
|
6355
|
-
key: "signalId",
|
|
6356
|
-
label: "Signal ID",
|
|
6357
|
-
format: (data) => data.signal.id,
|
|
6358
|
-
isVisible: () => true,
|
|
6359
|
-
},
|
|
6360
|
-
{
|
|
6361
|
-
key: "symbol",
|
|
6362
|
-
label: "Symbol",
|
|
6363
|
-
format: (data) => data.signal.symbol,
|
|
6364
|
-
isVisible: () => true,
|
|
6365
|
-
},
|
|
6366
|
-
{
|
|
6367
|
-
key: "position",
|
|
6368
|
-
label: "Position",
|
|
6369
|
-
format: (data) => data.signal.position.toUpperCase(),
|
|
6370
|
-
isVisible: () => true,
|
|
6371
|
-
},
|
|
6372
|
-
{
|
|
6373
|
-
key: "note",
|
|
6374
|
-
label: "Note",
|
|
6375
|
-
format: (data) => toPlainString(data.signal.note ?? "N/A"),
|
|
6376
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
6377
|
-
},
|
|
6378
|
-
{
|
|
6379
|
-
key: "openPrice",
|
|
6380
|
-
label: "Open Price",
|
|
6381
|
-
format: (data) => `${data.signal.priceOpen.toFixed(8)} USD`,
|
|
6382
|
-
isVisible: () => true,
|
|
6383
|
-
},
|
|
6384
|
-
{
|
|
6385
|
-
key: "closePrice",
|
|
6386
|
-
label: "Close Price",
|
|
6387
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
6388
|
-
isVisible: () => true,
|
|
6389
|
-
},
|
|
6390
|
-
{
|
|
6391
|
-
key: "takeProfit",
|
|
6392
|
-
label: "Take Profit",
|
|
6393
|
-
format: (data) => `${data.signal.priceTakeProfit.toFixed(8)} USD`,
|
|
6394
|
-
isVisible: () => true,
|
|
6395
|
-
},
|
|
6396
|
-
{
|
|
6397
|
-
key: "stopLoss",
|
|
6398
|
-
label: "Stop Loss",
|
|
6399
|
-
format: (data) => `${data.signal.priceStopLoss.toFixed(8)} USD`,
|
|
6400
|
-
isVisible: () => true,
|
|
6401
|
-
},
|
|
6402
|
-
{
|
|
6403
|
-
key: "pnl",
|
|
6404
|
-
label: "PNL (net)",
|
|
6405
|
-
format: (data) => {
|
|
6406
|
-
const pnlPercentage = data.pnl.pnlPercentage;
|
|
6407
|
-
return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
|
|
6408
|
-
},
|
|
6409
|
-
isVisible: () => true,
|
|
6410
|
-
},
|
|
6411
|
-
{
|
|
6412
|
-
key: "closeReason",
|
|
6413
|
-
label: "Close Reason",
|
|
6414
|
-
format: (data) => data.closeReason,
|
|
6415
|
-
isVisible: () => true,
|
|
6416
|
-
},
|
|
6417
|
-
{
|
|
6418
|
-
key: "duration",
|
|
6419
|
-
label: "Duration (min)",
|
|
6420
|
-
format: (data) => {
|
|
6421
|
-
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
6422
|
-
const durationMin = Math.round(durationMs / 60000);
|
|
6423
|
-
return `${durationMin}`;
|
|
6424
|
-
},
|
|
6425
|
-
isVisible: () => true,
|
|
6426
|
-
},
|
|
6427
|
-
{
|
|
6428
|
-
key: "openTimestamp",
|
|
6429
|
-
label: "Open Time",
|
|
6430
|
-
format: (data) => new Date(data.signal.pendingAt).toISOString(),
|
|
6431
|
-
isVisible: () => true,
|
|
6432
|
-
},
|
|
6433
|
-
{
|
|
6434
|
-
key: "closeTimestamp",
|
|
6435
|
-
label: "Close Time",
|
|
6436
|
-
format: (data) => new Date(data.closeTimestamp).toISOString(),
|
|
6437
|
-
isVisible: () => true,
|
|
6438
|
-
},
|
|
6439
|
-
];
|
|
7350
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
|
|
7351
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
|
|
7352
|
+
}
|
|
7353
|
+
return this.backtestLogicPublicService.run(symbol, context);
|
|
7354
|
+
};
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
|
|
7358
|
+
const METHOD_NAME_RUN = "walkerCommandService run";
|
|
7359
|
+
/**
|
|
7360
|
+
* Global service providing access to walker functionality.
|
|
7361
|
+
*
|
|
7362
|
+
* Simple wrapper around WalkerLogicPublicService for dependency injection.
|
|
7363
|
+
* Used by public API exports.
|
|
7364
|
+
*/
|
|
7365
|
+
class WalkerCommandService {
|
|
7366
|
+
constructor() {
|
|
7367
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
7368
|
+
this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
|
|
7369
|
+
this.walkerSchemaService = inject(TYPES.walkerSchemaService);
|
|
7370
|
+
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
7371
|
+
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
7372
|
+
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
7373
|
+
this.walkerValidationService = inject(TYPES.walkerValidationService);
|
|
7374
|
+
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
7375
|
+
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
7376
|
+
/**
|
|
7377
|
+
* Runs walker comparison for a symbol with context propagation.
|
|
7378
|
+
*
|
|
7379
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
7380
|
+
* @param context - Walker context with strategies and metric
|
|
7381
|
+
*/
|
|
7382
|
+
this.run = (symbol, context) => {
|
|
7383
|
+
this.loggerService.log(METHOD_NAME_RUN, {
|
|
7384
|
+
symbol,
|
|
7385
|
+
context,
|
|
7386
|
+
});
|
|
7387
|
+
{
|
|
7388
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
|
|
7389
|
+
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
|
|
7390
|
+
this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
|
|
7391
|
+
}
|
|
7392
|
+
{
|
|
7393
|
+
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
7394
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
7395
|
+
const { riskName, riskList } = this.strategySchemaService.get(strategyName);
|
|
7396
|
+
this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
|
|
7397
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
|
|
7398
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
return this.walkerLogicPublicService.run(symbol, context);
|
|
7402
|
+
};
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
7405
|
+
|
|
7406
|
+
/**
|
|
7407
|
+
* Checks if a value is unsafe for display (not a number, NaN, or Infinity).
|
|
7408
|
+
*
|
|
7409
|
+
* @param value - Value to check
|
|
7410
|
+
* @returns true if value is unsafe, false otherwise
|
|
7411
|
+
*/
|
|
7412
|
+
function isUnsafe$3(value) {
|
|
7413
|
+
if (typeof value !== "number") {
|
|
7414
|
+
return true;
|
|
7415
|
+
}
|
|
7416
|
+
if (isNaN(value)) {
|
|
7417
|
+
return true;
|
|
7418
|
+
}
|
|
7419
|
+
if (!isFinite(value)) {
|
|
7420
|
+
return true;
|
|
7421
|
+
}
|
|
7422
|
+
return false;
|
|
7423
|
+
}
|
|
6440
7424
|
/** Maximum number of signals to store in backtest reports */
|
|
6441
7425
|
const MAX_EVENTS$6 = 250;
|
|
6442
7426
|
/**
|
|
@@ -6530,9 +7514,10 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6530
7514
|
* Generates markdown report with all closed signals for a strategy (View).
|
|
6531
7515
|
*
|
|
6532
7516
|
* @param strategyName - Strategy name
|
|
7517
|
+
* @param columns - Column configuration for formatting the table
|
|
6533
7518
|
* @returns Markdown formatted report with all signals
|
|
6534
7519
|
*/
|
|
6535
|
-
async getReport(strategyName) {
|
|
7520
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.backtest_columns) {
|
|
6536
7521
|
const stats = await this.getData();
|
|
6537
7522
|
if (stats.totalSignals === 0) {
|
|
6538
7523
|
return [
|
|
@@ -6541,10 +7526,15 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6541
7526
|
"No signals closed yet."
|
|
6542
7527
|
].join("\n");
|
|
6543
7528
|
}
|
|
6544
|
-
const visibleColumns =
|
|
7529
|
+
const visibleColumns = [];
|
|
7530
|
+
for (const col of columns) {
|
|
7531
|
+
if (await col.isVisible()) {
|
|
7532
|
+
visibleColumns.push(col);
|
|
7533
|
+
}
|
|
7534
|
+
}
|
|
6545
7535
|
const header = visibleColumns.map((col) => col.label);
|
|
6546
7536
|
const separator = visibleColumns.map(() => "---");
|
|
6547
|
-
const rows = this._signalList.map((closedSignal) => visibleColumns.map((col) => col.format(closedSignal)));
|
|
7537
|
+
const rows = await Promise.all(this._signalList.map(async (closedSignal, index) => Promise.all(visibleColumns.map((col) => col.format(closedSignal, index)))));
|
|
6548
7538
|
const tableData = [header, separator, ...rows];
|
|
6549
7539
|
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
6550
7540
|
return [
|
|
@@ -6569,9 +7559,10 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6569
7559
|
*
|
|
6570
7560
|
* @param strategyName - Strategy name
|
|
6571
7561
|
* @param path - Directory path to save report (default: "./dump/backtest")
|
|
7562
|
+
* @param columns - Column configuration for formatting the table
|
|
6572
7563
|
*/
|
|
6573
|
-
async dump(strategyName, path$1 = "./dump/backtest") {
|
|
6574
|
-
const markdown = await this.getReport(strategyName);
|
|
7564
|
+
async dump(strategyName, path$1 = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
7565
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
6575
7566
|
try {
|
|
6576
7567
|
const dir = path.join(process.cwd(), path$1);
|
|
6577
7568
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -6679,6 +7670,7 @@ class BacktestMarkdownService {
|
|
|
6679
7670
|
*
|
|
6680
7671
|
* @param symbol - Trading pair symbol
|
|
6681
7672
|
* @param strategyName - Strategy name to generate report for
|
|
7673
|
+
* @param columns - Column configuration for formatting the table
|
|
6682
7674
|
* @returns Markdown formatted report string with table of all closed signals
|
|
6683
7675
|
*
|
|
6684
7676
|
* @example
|
|
@@ -6688,13 +7680,13 @@ class BacktestMarkdownService {
|
|
|
6688
7680
|
* console.log(markdown);
|
|
6689
7681
|
* ```
|
|
6690
7682
|
*/
|
|
6691
|
-
this.getReport = async (symbol, strategyName) => {
|
|
7683
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.backtest_columns) => {
|
|
6692
7684
|
this.loggerService.log("backtestMarkdownService getReport", {
|
|
6693
7685
|
symbol,
|
|
6694
7686
|
strategyName,
|
|
6695
7687
|
});
|
|
6696
7688
|
const storage = this.getStorage(symbol, strategyName);
|
|
6697
|
-
return storage.getReport(strategyName);
|
|
7689
|
+
return storage.getReport(strategyName, columns);
|
|
6698
7690
|
};
|
|
6699
7691
|
/**
|
|
6700
7692
|
* Saves symbol-strategy report to disk.
|
|
@@ -6704,6 +7696,7 @@ class BacktestMarkdownService {
|
|
|
6704
7696
|
* @param symbol - Trading pair symbol
|
|
6705
7697
|
* @param strategyName - Strategy name to save report for
|
|
6706
7698
|
* @param path - Directory path to save report (default: "./dump/backtest")
|
|
7699
|
+
* @param columns - Column configuration for formatting the table
|
|
6707
7700
|
*
|
|
6708
7701
|
* @example
|
|
6709
7702
|
* ```typescript
|
|
@@ -6716,14 +7709,14 @@ class BacktestMarkdownService {
|
|
|
6716
7709
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
6717
7710
|
* ```
|
|
6718
7711
|
*/
|
|
6719
|
-
this.dump = async (symbol, strategyName, path = "./dump/backtest") => {
|
|
7712
|
+
this.dump = async (symbol, strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) => {
|
|
6720
7713
|
this.loggerService.log("backtestMarkdownService dump", {
|
|
6721
7714
|
symbol,
|
|
6722
7715
|
strategyName,
|
|
6723
7716
|
path,
|
|
6724
7717
|
});
|
|
6725
7718
|
const storage = this.getStorage(symbol, strategyName);
|
|
6726
|
-
await storage.dump(strategyName, path);
|
|
7719
|
+
await storage.dump(strategyName, path, columns);
|
|
6727
7720
|
};
|
|
6728
7721
|
/**
|
|
6729
7722
|
* Clears accumulated signal data from storage.
|
|
@@ -6791,104 +7784,6 @@ function isUnsafe$2(value) {
|
|
|
6791
7784
|
}
|
|
6792
7785
|
return false;
|
|
6793
7786
|
}
|
|
6794
|
-
const columns$5 = [
|
|
6795
|
-
{
|
|
6796
|
-
key: "timestamp",
|
|
6797
|
-
label: "Timestamp",
|
|
6798
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
6799
|
-
isVisible: () => true,
|
|
6800
|
-
},
|
|
6801
|
-
{
|
|
6802
|
-
key: "action",
|
|
6803
|
-
label: "Action",
|
|
6804
|
-
format: (data) => data.action.toUpperCase(),
|
|
6805
|
-
isVisible: () => true,
|
|
6806
|
-
},
|
|
6807
|
-
{
|
|
6808
|
-
key: "symbol",
|
|
6809
|
-
label: "Symbol",
|
|
6810
|
-
format: (data) => data.symbol ?? "N/A",
|
|
6811
|
-
isVisible: () => true,
|
|
6812
|
-
},
|
|
6813
|
-
{
|
|
6814
|
-
key: "signalId",
|
|
6815
|
-
label: "Signal ID",
|
|
6816
|
-
format: (data) => data.signalId ?? "N/A",
|
|
6817
|
-
isVisible: () => true,
|
|
6818
|
-
},
|
|
6819
|
-
{
|
|
6820
|
-
key: "position",
|
|
6821
|
-
label: "Position",
|
|
6822
|
-
format: (data) => data.position?.toUpperCase() ?? "N/A",
|
|
6823
|
-
isVisible: () => true,
|
|
6824
|
-
},
|
|
6825
|
-
{
|
|
6826
|
-
key: "note",
|
|
6827
|
-
label: "Note",
|
|
6828
|
-
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
6829
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
6830
|
-
},
|
|
6831
|
-
{
|
|
6832
|
-
key: "currentPrice",
|
|
6833
|
-
label: "Current Price",
|
|
6834
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
6835
|
-
isVisible: () => true,
|
|
6836
|
-
},
|
|
6837
|
-
{
|
|
6838
|
-
key: "openPrice",
|
|
6839
|
-
label: "Open Price",
|
|
6840
|
-
format: (data) => data.openPrice !== undefined ? `${data.openPrice.toFixed(8)} USD` : "N/A",
|
|
6841
|
-
isVisible: () => true,
|
|
6842
|
-
},
|
|
6843
|
-
{
|
|
6844
|
-
key: "takeProfit",
|
|
6845
|
-
label: "Take Profit",
|
|
6846
|
-
format: (data) => data.takeProfit !== undefined
|
|
6847
|
-
? `${data.takeProfit.toFixed(8)} USD`
|
|
6848
|
-
: "N/A",
|
|
6849
|
-
isVisible: () => true,
|
|
6850
|
-
},
|
|
6851
|
-
{
|
|
6852
|
-
key: "stopLoss",
|
|
6853
|
-
label: "Stop Loss",
|
|
6854
|
-
format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
|
|
6855
|
-
isVisible: () => true,
|
|
6856
|
-
},
|
|
6857
|
-
{
|
|
6858
|
-
key: "percentTp",
|
|
6859
|
-
label: "% to TP",
|
|
6860
|
-
format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
|
|
6861
|
-
isVisible: () => true,
|
|
6862
|
-
},
|
|
6863
|
-
{
|
|
6864
|
-
key: "percentSl",
|
|
6865
|
-
label: "% to SL",
|
|
6866
|
-
format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
|
|
6867
|
-
isVisible: () => true,
|
|
6868
|
-
},
|
|
6869
|
-
{
|
|
6870
|
-
key: "pnl",
|
|
6871
|
-
label: "PNL (net)",
|
|
6872
|
-
format: (data) => {
|
|
6873
|
-
if (data.pnl === undefined)
|
|
6874
|
-
return "N/A";
|
|
6875
|
-
return `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`;
|
|
6876
|
-
},
|
|
6877
|
-
isVisible: () => true,
|
|
6878
|
-
},
|
|
6879
|
-
{
|
|
6880
|
-
key: "closeReason",
|
|
6881
|
-
label: "Close Reason",
|
|
6882
|
-
format: (data) => data.closeReason ?? "N/A",
|
|
6883
|
-
isVisible: () => true,
|
|
6884
|
-
},
|
|
6885
|
-
{
|
|
6886
|
-
key: "duration",
|
|
6887
|
-
label: "Duration (min)",
|
|
6888
|
-
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
6889
|
-
isVisible: () => true,
|
|
6890
|
-
},
|
|
6891
|
-
];
|
|
6892
7787
|
/** Maximum number of events to store in live trading reports */
|
|
6893
7788
|
const MAX_EVENTS$5 = 250;
|
|
6894
7789
|
/**
|
|
@@ -7100,9 +7995,10 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7100
7995
|
* Generates markdown report with all tick events for a strategy (View).
|
|
7101
7996
|
*
|
|
7102
7997
|
* @param strategyName - Strategy name
|
|
7998
|
+
* @param columns - Column configuration for formatting the table
|
|
7103
7999
|
* @returns Markdown formatted report with all events
|
|
7104
8000
|
*/
|
|
7105
|
-
async getReport(strategyName) {
|
|
8001
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.live_columns) {
|
|
7106
8002
|
const stats = await this.getData();
|
|
7107
8003
|
if (stats.totalEvents === 0) {
|
|
7108
8004
|
return [
|
|
@@ -7111,10 +8007,15 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7111
8007
|
"No events recorded yet."
|
|
7112
8008
|
].join("\n");
|
|
7113
8009
|
}
|
|
7114
|
-
const visibleColumns =
|
|
8010
|
+
const visibleColumns = [];
|
|
8011
|
+
for (const col of columns) {
|
|
8012
|
+
if (await col.isVisible()) {
|
|
8013
|
+
visibleColumns.push(col);
|
|
8014
|
+
}
|
|
8015
|
+
}
|
|
7115
8016
|
const header = visibleColumns.map((col) => col.label);
|
|
7116
8017
|
const separator = visibleColumns.map(() => "---");
|
|
7117
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
8018
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
7118
8019
|
const tableData = [header, separator, ...rows];
|
|
7119
8020
|
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
7120
8021
|
return [
|
|
@@ -7139,9 +8040,10 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7139
8040
|
*
|
|
7140
8041
|
* @param strategyName - Strategy name
|
|
7141
8042
|
* @param path - Directory path to save report (default: "./dump/live")
|
|
8043
|
+
* @param columns - Column configuration for formatting the table
|
|
7142
8044
|
*/
|
|
7143
|
-
async dump(strategyName, path$1 = "./dump/live") {
|
|
7144
|
-
const markdown = await this.getReport(strategyName);
|
|
8045
|
+
async dump(strategyName, path$1 = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
8046
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
7145
8047
|
try {
|
|
7146
8048
|
const dir = path.join(process.cwd(), path$1);
|
|
7147
8049
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -7262,6 +8164,7 @@ class LiveMarkdownService {
|
|
|
7262
8164
|
*
|
|
7263
8165
|
* @param symbol - Trading pair symbol
|
|
7264
8166
|
* @param strategyName - Strategy name to generate report for
|
|
8167
|
+
* @param columns - Column configuration for formatting the table
|
|
7265
8168
|
* @returns Markdown formatted report string with table of all events
|
|
7266
8169
|
*
|
|
7267
8170
|
* @example
|
|
@@ -7271,13 +8174,13 @@ class LiveMarkdownService {
|
|
|
7271
8174
|
* console.log(markdown);
|
|
7272
8175
|
* ```
|
|
7273
8176
|
*/
|
|
7274
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8177
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.live_columns) => {
|
|
7275
8178
|
this.loggerService.log("liveMarkdownService getReport", {
|
|
7276
8179
|
symbol,
|
|
7277
8180
|
strategyName,
|
|
7278
8181
|
});
|
|
7279
8182
|
const storage = this.getStorage(symbol, strategyName);
|
|
7280
|
-
return storage.getReport(strategyName);
|
|
8183
|
+
return storage.getReport(strategyName, columns);
|
|
7281
8184
|
};
|
|
7282
8185
|
/**
|
|
7283
8186
|
* Saves symbol-strategy report to disk.
|
|
@@ -7287,6 +8190,7 @@ class LiveMarkdownService {
|
|
|
7287
8190
|
* @param symbol - Trading pair symbol
|
|
7288
8191
|
* @param strategyName - Strategy name to save report for
|
|
7289
8192
|
* @param path - Directory path to save report (default: "./dump/live")
|
|
8193
|
+
* @param columns - Column configuration for formatting the table
|
|
7290
8194
|
*
|
|
7291
8195
|
* @example
|
|
7292
8196
|
* ```typescript
|
|
@@ -7299,14 +8203,14 @@ class LiveMarkdownService {
|
|
|
7299
8203
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
7300
8204
|
* ```
|
|
7301
8205
|
*/
|
|
7302
|
-
this.dump = async (symbol, strategyName, path = "./dump/live") => {
|
|
8206
|
+
this.dump = async (symbol, strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) => {
|
|
7303
8207
|
this.loggerService.log("liveMarkdownService dump", {
|
|
7304
8208
|
symbol,
|
|
7305
8209
|
strategyName,
|
|
7306
8210
|
path,
|
|
7307
8211
|
});
|
|
7308
8212
|
const storage = this.getStorage(symbol, strategyName);
|
|
7309
|
-
await storage.dump(strategyName, path);
|
|
8213
|
+
await storage.dump(strategyName, path, columns);
|
|
7310
8214
|
};
|
|
7311
8215
|
/**
|
|
7312
8216
|
* Clears accumulated event data from storage.
|
|
@@ -7342,88 +8246,20 @@ class LiveMarkdownService {
|
|
|
7342
8246
|
* Initializes the service by subscribing to live signal events.
|
|
7343
8247
|
* Uses singleshot to ensure initialization happens only once.
|
|
7344
8248
|
* Automatically called on first use.
|
|
7345
|
-
*
|
|
7346
|
-
* @example
|
|
7347
|
-
* ```typescript
|
|
7348
|
-
* const service = new LiveMarkdownService();
|
|
7349
|
-
* await service.init(); // Subscribe to live events
|
|
7350
|
-
* ```
|
|
7351
|
-
*/
|
|
7352
|
-
this.init = functoolsKit.singleshot(async () => {
|
|
7353
|
-
this.loggerService.log("liveMarkdownService init");
|
|
7354
|
-
signalLiveEmitter.subscribe(this.tick);
|
|
7355
|
-
});
|
|
7356
|
-
}
|
|
7357
|
-
}
|
|
7358
|
-
|
|
7359
|
-
const columns$4 = [
|
|
7360
|
-
{
|
|
7361
|
-
key: "timestamp",
|
|
7362
|
-
label: "Timestamp",
|
|
7363
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
7364
|
-
isVisible: () => true,
|
|
7365
|
-
},
|
|
7366
|
-
{
|
|
7367
|
-
key: "action",
|
|
7368
|
-
label: "Action",
|
|
7369
|
-
format: (data) => data.action.toUpperCase(),
|
|
7370
|
-
isVisible: () => true,
|
|
7371
|
-
},
|
|
7372
|
-
{
|
|
7373
|
-
key: "symbol",
|
|
7374
|
-
label: "Symbol",
|
|
7375
|
-
format: (data) => data.symbol,
|
|
7376
|
-
isVisible: () => true,
|
|
7377
|
-
},
|
|
7378
|
-
{
|
|
7379
|
-
key: "signalId",
|
|
7380
|
-
label: "Signal ID",
|
|
7381
|
-
format: (data) => data.signalId,
|
|
7382
|
-
isVisible: () => true,
|
|
7383
|
-
},
|
|
7384
|
-
{
|
|
7385
|
-
key: "position",
|
|
7386
|
-
label: "Position",
|
|
7387
|
-
format: (data) => data.position.toUpperCase(),
|
|
7388
|
-
isVisible: () => true,
|
|
7389
|
-
},
|
|
7390
|
-
{
|
|
7391
|
-
key: "note",
|
|
7392
|
-
label: "Note",
|
|
7393
|
-
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
7394
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
7395
|
-
},
|
|
7396
|
-
{
|
|
7397
|
-
key: "currentPrice",
|
|
7398
|
-
label: "Current Price",
|
|
7399
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
7400
|
-
isVisible: () => true,
|
|
7401
|
-
},
|
|
7402
|
-
{
|
|
7403
|
-
key: "priceOpen",
|
|
7404
|
-
label: "Entry Price",
|
|
7405
|
-
format: (data) => `${data.priceOpen.toFixed(8)} USD`,
|
|
7406
|
-
isVisible: () => true,
|
|
7407
|
-
},
|
|
7408
|
-
{
|
|
7409
|
-
key: "takeProfit",
|
|
7410
|
-
label: "Take Profit",
|
|
7411
|
-
format: (data) => `${data.takeProfit.toFixed(8)} USD`,
|
|
7412
|
-
isVisible: () => true,
|
|
7413
|
-
},
|
|
7414
|
-
{
|
|
7415
|
-
key: "stopLoss",
|
|
7416
|
-
label: "Stop Loss",
|
|
7417
|
-
format: (data) => `${data.stopLoss.toFixed(8)} USD`,
|
|
7418
|
-
isVisible: () => true,
|
|
7419
|
-
},
|
|
7420
|
-
{
|
|
7421
|
-
key: "duration",
|
|
7422
|
-
label: "Wait Time (min)",
|
|
7423
|
-
format: (data) => data.duration !== undefined ? `${data.duration}` : "N/A",
|
|
7424
|
-
isVisible: () => true,
|
|
7425
|
-
},
|
|
7426
|
-
];
|
|
8249
|
+
*
|
|
8250
|
+
* @example
|
|
8251
|
+
* ```typescript
|
|
8252
|
+
* const service = new LiveMarkdownService();
|
|
8253
|
+
* await service.init(); // Subscribe to live events
|
|
8254
|
+
* ```
|
|
8255
|
+
*/
|
|
8256
|
+
this.init = functoolsKit.singleshot(async () => {
|
|
8257
|
+
this.loggerService.log("liveMarkdownService init");
|
|
8258
|
+
signalLiveEmitter.subscribe(this.tick);
|
|
8259
|
+
});
|
|
8260
|
+
}
|
|
8261
|
+
}
|
|
8262
|
+
|
|
7427
8263
|
/** Maximum number of events to store in schedule reports */
|
|
7428
8264
|
const MAX_EVENTS$4 = 250;
|
|
7429
8265
|
/**
|
|
@@ -7568,9 +8404,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7568
8404
|
* Generates markdown report with all scheduled events for a strategy (View).
|
|
7569
8405
|
*
|
|
7570
8406
|
* @param strategyName - Strategy name
|
|
8407
|
+
* @param columns - Column configuration for formatting the table
|
|
7571
8408
|
* @returns Markdown formatted report with all events
|
|
7572
8409
|
*/
|
|
7573
|
-
async getReport(strategyName) {
|
|
8410
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.schedule_columns) {
|
|
7574
8411
|
const stats = await this.getData();
|
|
7575
8412
|
if (stats.totalEvents === 0) {
|
|
7576
8413
|
return [
|
|
@@ -7579,10 +8416,15 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7579
8416
|
"No scheduled signals recorded yet."
|
|
7580
8417
|
].join("\n");
|
|
7581
8418
|
}
|
|
7582
|
-
const visibleColumns =
|
|
8419
|
+
const visibleColumns = [];
|
|
8420
|
+
for (const col of columns) {
|
|
8421
|
+
if (await col.isVisible()) {
|
|
8422
|
+
visibleColumns.push(col);
|
|
8423
|
+
}
|
|
8424
|
+
}
|
|
7583
8425
|
const header = visibleColumns.map((col) => col.label);
|
|
7584
8426
|
const separator = visibleColumns.map(() => "---");
|
|
7585
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
8427
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
7586
8428
|
const tableData = [header, separator, ...rows];
|
|
7587
8429
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
7588
8430
|
return [
|
|
@@ -7605,9 +8447,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7605
8447
|
*
|
|
7606
8448
|
* @param strategyName - Strategy name
|
|
7607
8449
|
* @param path - Directory path to save report (default: "./dump/schedule")
|
|
8450
|
+
* @param columns - Column configuration for formatting the table
|
|
7608
8451
|
*/
|
|
7609
|
-
async dump(strategyName, path$1 = "./dump/schedule") {
|
|
7610
|
-
const markdown = await this.getReport(strategyName);
|
|
8452
|
+
async dump(strategyName, path$1 = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
8453
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
7611
8454
|
try {
|
|
7612
8455
|
const dir = path.join(process.cwd(), path$1);
|
|
7613
8456
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -7713,6 +8556,7 @@ class ScheduleMarkdownService {
|
|
|
7713
8556
|
*
|
|
7714
8557
|
* @param symbol - Trading pair symbol
|
|
7715
8558
|
* @param strategyName - Strategy name to generate report for
|
|
8559
|
+
* @param columns - Column configuration for formatting the table
|
|
7716
8560
|
* @returns Markdown formatted report string with table of all events
|
|
7717
8561
|
*
|
|
7718
8562
|
* @example
|
|
@@ -7722,13 +8566,13 @@ class ScheduleMarkdownService {
|
|
|
7722
8566
|
* console.log(markdown);
|
|
7723
8567
|
* ```
|
|
7724
8568
|
*/
|
|
7725
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8569
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.schedule_columns) => {
|
|
7726
8570
|
this.loggerService.log("scheduleMarkdownService getReport", {
|
|
7727
8571
|
symbol,
|
|
7728
8572
|
strategyName,
|
|
7729
8573
|
});
|
|
7730
8574
|
const storage = this.getStorage(symbol, strategyName);
|
|
7731
|
-
return storage.getReport(strategyName);
|
|
8575
|
+
return storage.getReport(strategyName, columns);
|
|
7732
8576
|
};
|
|
7733
8577
|
/**
|
|
7734
8578
|
* Saves symbol-strategy report to disk.
|
|
@@ -7738,6 +8582,7 @@ class ScheduleMarkdownService {
|
|
|
7738
8582
|
* @param symbol - Trading pair symbol
|
|
7739
8583
|
* @param strategyName - Strategy name to save report for
|
|
7740
8584
|
* @param path - Directory path to save report (default: "./dump/schedule")
|
|
8585
|
+
* @param columns - Column configuration for formatting the table
|
|
7741
8586
|
*
|
|
7742
8587
|
* @example
|
|
7743
8588
|
* ```typescript
|
|
@@ -7750,14 +8595,14 @@ class ScheduleMarkdownService {
|
|
|
7750
8595
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
7751
8596
|
* ```
|
|
7752
8597
|
*/
|
|
7753
|
-
this.dump = async (symbol, strategyName, path = "./dump/schedule") => {
|
|
8598
|
+
this.dump = async (symbol, strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) => {
|
|
7754
8599
|
this.loggerService.log("scheduleMarkdownService dump", {
|
|
7755
8600
|
symbol,
|
|
7756
8601
|
strategyName,
|
|
7757
8602
|
path,
|
|
7758
8603
|
});
|
|
7759
8604
|
const storage = this.getStorage(symbol, strategyName);
|
|
7760
|
-
await storage.dump(strategyName, path);
|
|
8605
|
+
await storage.dump(strategyName, path, columns);
|
|
7761
8606
|
};
|
|
7762
8607
|
/**
|
|
7763
8608
|
* Clears accumulated event data from storage.
|
|
@@ -7816,86 +8661,6 @@ function percentile(sortedArray, p) {
|
|
|
7816
8661
|
const index = Math.ceil((sortedArray.length * p) / 100) - 1;
|
|
7817
8662
|
return sortedArray[Math.max(0, index)];
|
|
7818
8663
|
}
|
|
7819
|
-
const columns$3 = [
|
|
7820
|
-
{
|
|
7821
|
-
key: "metricType",
|
|
7822
|
-
label: "Metric Type",
|
|
7823
|
-
format: (data) => data.metricType,
|
|
7824
|
-
isVisible: () => true,
|
|
7825
|
-
},
|
|
7826
|
-
{
|
|
7827
|
-
key: "count",
|
|
7828
|
-
label: "Count",
|
|
7829
|
-
format: (data) => data.count.toString(),
|
|
7830
|
-
isVisible: () => true,
|
|
7831
|
-
},
|
|
7832
|
-
{
|
|
7833
|
-
key: "totalDuration",
|
|
7834
|
-
label: "Total (ms)",
|
|
7835
|
-
format: (data) => data.totalDuration.toFixed(2),
|
|
7836
|
-
isVisible: () => true,
|
|
7837
|
-
},
|
|
7838
|
-
{
|
|
7839
|
-
key: "avgDuration",
|
|
7840
|
-
label: "Avg (ms)",
|
|
7841
|
-
format: (data) => data.avgDuration.toFixed(2),
|
|
7842
|
-
isVisible: () => true,
|
|
7843
|
-
},
|
|
7844
|
-
{
|
|
7845
|
-
key: "minDuration",
|
|
7846
|
-
label: "Min (ms)",
|
|
7847
|
-
format: (data) => data.minDuration.toFixed(2),
|
|
7848
|
-
isVisible: () => true,
|
|
7849
|
-
},
|
|
7850
|
-
{
|
|
7851
|
-
key: "maxDuration",
|
|
7852
|
-
label: "Max (ms)",
|
|
7853
|
-
format: (data) => data.maxDuration.toFixed(2),
|
|
7854
|
-
isVisible: () => true,
|
|
7855
|
-
},
|
|
7856
|
-
{
|
|
7857
|
-
key: "stdDev",
|
|
7858
|
-
label: "Std Dev (ms)",
|
|
7859
|
-
format: (data) => data.stdDev.toFixed(2),
|
|
7860
|
-
isVisible: () => true,
|
|
7861
|
-
},
|
|
7862
|
-
{
|
|
7863
|
-
key: "median",
|
|
7864
|
-
label: "Median (ms)",
|
|
7865
|
-
format: (data) => data.median.toFixed(2),
|
|
7866
|
-
isVisible: () => true,
|
|
7867
|
-
},
|
|
7868
|
-
{
|
|
7869
|
-
key: "p95",
|
|
7870
|
-
label: "P95 (ms)",
|
|
7871
|
-
format: (data) => data.p95.toFixed(2),
|
|
7872
|
-
isVisible: () => true,
|
|
7873
|
-
},
|
|
7874
|
-
{
|
|
7875
|
-
key: "p99",
|
|
7876
|
-
label: "P99 (ms)",
|
|
7877
|
-
format: (data) => data.p99.toFixed(2),
|
|
7878
|
-
isVisible: () => true,
|
|
7879
|
-
},
|
|
7880
|
-
{
|
|
7881
|
-
key: "avgWaitTime",
|
|
7882
|
-
label: "Avg Wait (ms)",
|
|
7883
|
-
format: (data) => data.avgWaitTime.toFixed(2),
|
|
7884
|
-
isVisible: () => true,
|
|
7885
|
-
},
|
|
7886
|
-
{
|
|
7887
|
-
key: "minWaitTime",
|
|
7888
|
-
label: "Min Wait (ms)",
|
|
7889
|
-
format: (data) => data.minWaitTime.toFixed(2),
|
|
7890
|
-
isVisible: () => true,
|
|
7891
|
-
},
|
|
7892
|
-
{
|
|
7893
|
-
key: "maxWaitTime",
|
|
7894
|
-
label: "Max Wait (ms)",
|
|
7895
|
-
format: (data) => data.maxWaitTime.toFixed(2),
|
|
7896
|
-
isVisible: () => true,
|
|
7897
|
-
},
|
|
7898
|
-
];
|
|
7899
8664
|
/** Maximum number of performance events to store per strategy */
|
|
7900
8665
|
const MAX_EVENTS$3 = 10000;
|
|
7901
8666
|
/**
|
|
@@ -7998,9 +8763,10 @@ class PerformanceStorage {
|
|
|
7998
8763
|
* Generates markdown report with performance statistics.
|
|
7999
8764
|
*
|
|
8000
8765
|
* @param strategyName - Strategy name
|
|
8766
|
+
* @param columns - Column configuration for formatting the table
|
|
8001
8767
|
* @returns Markdown formatted report
|
|
8002
8768
|
*/
|
|
8003
|
-
async getReport(strategyName) {
|
|
8769
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.performance_columns) {
|
|
8004
8770
|
const stats = await this.getData(strategyName);
|
|
8005
8771
|
if (stats.totalEvents === 0) {
|
|
8006
8772
|
return [
|
|
@@ -8012,10 +8778,15 @@ class PerformanceStorage {
|
|
|
8012
8778
|
// Sort metrics by total duration (descending) to show bottlenecks first
|
|
8013
8779
|
const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
|
|
8014
8780
|
// Generate summary table using Column interface
|
|
8015
|
-
const visibleColumns =
|
|
8781
|
+
const visibleColumns = [];
|
|
8782
|
+
for (const col of columns) {
|
|
8783
|
+
if (await col.isVisible()) {
|
|
8784
|
+
visibleColumns.push(col);
|
|
8785
|
+
}
|
|
8786
|
+
}
|
|
8016
8787
|
const header = visibleColumns.map((col) => col.label);
|
|
8017
8788
|
const separator = visibleColumns.map(() => "---");
|
|
8018
|
-
const rows = sortedMetrics.map((metric) => visibleColumns.map((col) => col.format(metric)));
|
|
8789
|
+
const rows = await Promise.all(sortedMetrics.map(async (metric, index) => Promise.all(visibleColumns.map((col) => col.format(metric, index)))));
|
|
8019
8790
|
const tableData = [header, separator, ...rows];
|
|
8020
8791
|
const summaryTable = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8021
8792
|
// Calculate percentage of total time for each metric
|
|
@@ -8046,9 +8817,10 @@ class PerformanceStorage {
|
|
|
8046
8817
|
*
|
|
8047
8818
|
* @param strategyName - Strategy name
|
|
8048
8819
|
* @param path - Directory path to save report
|
|
8820
|
+
* @param columns - Column configuration for formatting the table
|
|
8049
8821
|
*/
|
|
8050
|
-
async dump(strategyName, path$1 = "./dump/performance") {
|
|
8051
|
-
const markdown = await this.getReport(strategyName);
|
|
8822
|
+
async dump(strategyName, path$1 = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
8823
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
8052
8824
|
try {
|
|
8053
8825
|
const dir = path.join(process.cwd(), path$1);
|
|
8054
8826
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -8141,6 +8913,7 @@ class PerformanceMarkdownService {
|
|
|
8141
8913
|
*
|
|
8142
8914
|
* @param symbol - Trading pair symbol
|
|
8143
8915
|
* @param strategyName - Strategy name to generate report for
|
|
8916
|
+
* @param columns - Column configuration for formatting the table
|
|
8144
8917
|
* @returns Markdown formatted report string
|
|
8145
8918
|
*
|
|
8146
8919
|
* @example
|
|
@@ -8149,13 +8922,13 @@ class PerformanceMarkdownService {
|
|
|
8149
8922
|
* console.log(markdown);
|
|
8150
8923
|
* ```
|
|
8151
8924
|
*/
|
|
8152
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8925
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.performance_columns) => {
|
|
8153
8926
|
this.loggerService.log("performanceMarkdownService getReport", {
|
|
8154
8927
|
symbol,
|
|
8155
8928
|
strategyName,
|
|
8156
8929
|
});
|
|
8157
8930
|
const storage = this.getStorage(symbol, strategyName);
|
|
8158
|
-
return storage.getReport(strategyName);
|
|
8931
|
+
return storage.getReport(strategyName, columns);
|
|
8159
8932
|
};
|
|
8160
8933
|
/**
|
|
8161
8934
|
* Saves performance report to disk.
|
|
@@ -8163,6 +8936,7 @@ class PerformanceMarkdownService {
|
|
|
8163
8936
|
* @param symbol - Trading pair symbol
|
|
8164
8937
|
* @param strategyName - Strategy name to save report for
|
|
8165
8938
|
* @param path - Directory path to save report
|
|
8939
|
+
* @param columns - Column configuration for formatting the table
|
|
8166
8940
|
*
|
|
8167
8941
|
* @example
|
|
8168
8942
|
* ```typescript
|
|
@@ -8173,14 +8947,14 @@ class PerformanceMarkdownService {
|
|
|
8173
8947
|
* await performanceService.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
8174
8948
|
* ```
|
|
8175
8949
|
*/
|
|
8176
|
-
this.dump = async (symbol, strategyName, path = "./dump/performance") => {
|
|
8950
|
+
this.dump = async (symbol, strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) => {
|
|
8177
8951
|
this.loggerService.log("performanceMarkdownService dump", {
|
|
8178
8952
|
symbol,
|
|
8179
8953
|
strategyName,
|
|
8180
8954
|
path,
|
|
8181
8955
|
});
|
|
8182
8956
|
const storage = this.getStorage(symbol, strategyName);
|
|
8183
|
-
await storage.dump(strategyName, path);
|
|
8957
|
+
await storage.dump(strategyName, path, columns);
|
|
8184
8958
|
};
|
|
8185
8959
|
/**
|
|
8186
8960
|
* Clears accumulated performance data from storage.
|
|
@@ -8239,135 +9013,6 @@ function formatMetric(value) {
|
|
|
8239
9013
|
}
|
|
8240
9014
|
return value.toFixed(2);
|
|
8241
9015
|
}
|
|
8242
|
-
/**
|
|
8243
|
-
* Creates strategy comparison columns based on metric name.
|
|
8244
|
-
* Dynamically builds column configuration with metric-specific header.
|
|
8245
|
-
*
|
|
8246
|
-
* @param metric - Metric being optimized
|
|
8247
|
-
* @returns Array of column configurations for strategy comparison table
|
|
8248
|
-
*/
|
|
8249
|
-
function createStrategyColumns(metric) {
|
|
8250
|
-
return [
|
|
8251
|
-
{
|
|
8252
|
-
key: "rank",
|
|
8253
|
-
label: "Rank",
|
|
8254
|
-
format: (data, index) => `${index + 1}`,
|
|
8255
|
-
isVisible: () => true,
|
|
8256
|
-
},
|
|
8257
|
-
{
|
|
8258
|
-
key: "strategy",
|
|
8259
|
-
label: "Strategy",
|
|
8260
|
-
format: (data) => data.strategyName,
|
|
8261
|
-
isVisible: () => true,
|
|
8262
|
-
},
|
|
8263
|
-
{
|
|
8264
|
-
key: "metric",
|
|
8265
|
-
label: metric,
|
|
8266
|
-
format: (data) => formatMetric(data.metricValue),
|
|
8267
|
-
isVisible: () => true,
|
|
8268
|
-
},
|
|
8269
|
-
{
|
|
8270
|
-
key: "totalSignals",
|
|
8271
|
-
label: "Total Signals",
|
|
8272
|
-
format: (data) => `${data.stats.totalSignals}`,
|
|
8273
|
-
isVisible: () => true,
|
|
8274
|
-
},
|
|
8275
|
-
{
|
|
8276
|
-
key: "winRate",
|
|
8277
|
-
label: "Win Rate",
|
|
8278
|
-
format: (data) => data.stats.winRate !== null
|
|
8279
|
-
? `${data.stats.winRate.toFixed(2)}%`
|
|
8280
|
-
: "N/A",
|
|
8281
|
-
isVisible: () => true,
|
|
8282
|
-
},
|
|
8283
|
-
{
|
|
8284
|
-
key: "avgPnl",
|
|
8285
|
-
label: "Avg PNL",
|
|
8286
|
-
format: (data) => data.stats.avgPnl !== null
|
|
8287
|
-
? `${data.stats.avgPnl > 0 ? "+" : ""}${data.stats.avgPnl.toFixed(2)}%`
|
|
8288
|
-
: "N/A",
|
|
8289
|
-
isVisible: () => true,
|
|
8290
|
-
},
|
|
8291
|
-
{
|
|
8292
|
-
key: "totalPnl",
|
|
8293
|
-
label: "Total PNL",
|
|
8294
|
-
format: (data) => data.stats.totalPnl !== null
|
|
8295
|
-
? `${data.stats.totalPnl > 0 ? "+" : ""}${data.stats.totalPnl.toFixed(2)}%`
|
|
8296
|
-
: "N/A",
|
|
8297
|
-
isVisible: () => true,
|
|
8298
|
-
},
|
|
8299
|
-
{
|
|
8300
|
-
key: "sharpeRatio",
|
|
8301
|
-
label: "Sharpe Ratio",
|
|
8302
|
-
format: (data) => data.stats.sharpeRatio !== null
|
|
8303
|
-
? `${data.stats.sharpeRatio.toFixed(3)}`
|
|
8304
|
-
: "N/A",
|
|
8305
|
-
isVisible: () => true,
|
|
8306
|
-
},
|
|
8307
|
-
{
|
|
8308
|
-
key: "stdDev",
|
|
8309
|
-
label: "Std Dev",
|
|
8310
|
-
format: (data) => data.stats.stdDev !== null
|
|
8311
|
-
? `${data.stats.stdDev.toFixed(3)}%`
|
|
8312
|
-
: "N/A",
|
|
8313
|
-
isVisible: () => true,
|
|
8314
|
-
},
|
|
8315
|
-
];
|
|
8316
|
-
}
|
|
8317
|
-
/**
|
|
8318
|
-
* Column configuration for PNL table.
|
|
8319
|
-
* Defines all columns for displaying closed signals across strategies.
|
|
8320
|
-
*/
|
|
8321
|
-
const pnlColumns = [
|
|
8322
|
-
{
|
|
8323
|
-
key: "strategy",
|
|
8324
|
-
label: "Strategy",
|
|
8325
|
-
format: (data) => data.strategyName,
|
|
8326
|
-
isVisible: () => true,
|
|
8327
|
-
},
|
|
8328
|
-
{
|
|
8329
|
-
key: "signalId",
|
|
8330
|
-
label: "Signal ID",
|
|
8331
|
-
format: (data) => data.signalId,
|
|
8332
|
-
isVisible: () => true,
|
|
8333
|
-
},
|
|
8334
|
-
{
|
|
8335
|
-
key: "symbol",
|
|
8336
|
-
label: "Symbol",
|
|
8337
|
-
format: (data) => data.symbol,
|
|
8338
|
-
isVisible: () => true,
|
|
8339
|
-
},
|
|
8340
|
-
{
|
|
8341
|
-
key: "position",
|
|
8342
|
-
label: "Position",
|
|
8343
|
-
format: (data) => data.position.toUpperCase(),
|
|
8344
|
-
isVisible: () => true,
|
|
8345
|
-
},
|
|
8346
|
-
{
|
|
8347
|
-
key: "pnl",
|
|
8348
|
-
label: "PNL (net)",
|
|
8349
|
-
format: (data) => `${data.pnl > 0 ? "+" : ""}${data.pnl.toFixed(2)}%`,
|
|
8350
|
-
isVisible: () => true,
|
|
8351
|
-
},
|
|
8352
|
-
{
|
|
8353
|
-
key: "closeReason",
|
|
8354
|
-
label: "Close Reason",
|
|
8355
|
-
format: (data) => data.closeReason,
|
|
8356
|
-
isVisible: () => true,
|
|
8357
|
-
},
|
|
8358
|
-
{
|
|
8359
|
-
key: "openTime",
|
|
8360
|
-
label: "Open Time",
|
|
8361
|
-
format: (data) => new Date(data.openTime).toISOString(),
|
|
8362
|
-
isVisible: () => true,
|
|
8363
|
-
},
|
|
8364
|
-
{
|
|
8365
|
-
key: "closeTime",
|
|
8366
|
-
label: "Close Time",
|
|
8367
|
-
format: (data) => new Date(data.closeTime).toISOString(),
|
|
8368
|
-
isVisible: () => true,
|
|
8369
|
-
},
|
|
8370
|
-
];
|
|
8371
9016
|
/**
|
|
8372
9017
|
* Storage class for accumulating walker results.
|
|
8373
9018
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
@@ -8399,7 +9044,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8399
9044
|
this._strategyResults.unshift({
|
|
8400
9045
|
strategyName: data.strategyName,
|
|
8401
9046
|
stats: data.stats,
|
|
8402
|
-
metricValue: data.metricValue,
|
|
9047
|
+
metricValue: isUnsafe$1(data.metricValue) ? null : data.metricValue,
|
|
8403
9048
|
});
|
|
8404
9049
|
}
|
|
8405
9050
|
/**
|
|
@@ -8432,11 +9077,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8432
9077
|
* Generates comparison table for top N strategies (View).
|
|
8433
9078
|
* Sorts strategies by metric value and formats as markdown table.
|
|
8434
9079
|
*
|
|
8435
|
-
* @param metric - Metric being optimized
|
|
8436
9080
|
* @param topN - Number of top strategies to include (default: 10)
|
|
9081
|
+
* @param columns - Column configuration for formatting the strategy comparison table
|
|
8437
9082
|
* @returns Markdown formatted comparison table
|
|
8438
9083
|
*/
|
|
8439
|
-
getComparisonTable(
|
|
9084
|
+
async getComparisonTable(topN = 10, columns = COLUMN_CONFIG.walker_strategy_columns) {
|
|
8440
9085
|
if (this._strategyResults.length === 0) {
|
|
8441
9086
|
return "No strategy results available.";
|
|
8442
9087
|
}
|
|
@@ -8449,13 +9094,17 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8449
9094
|
// Take top N strategies
|
|
8450
9095
|
const topStrategies = sortedResults.slice(0, topN);
|
|
8451
9096
|
// Get columns configuration
|
|
8452
|
-
const
|
|
8453
|
-
const
|
|
9097
|
+
const visibleColumns = [];
|
|
9098
|
+
for (const col of columns) {
|
|
9099
|
+
if (await col.isVisible()) {
|
|
9100
|
+
visibleColumns.push(col);
|
|
9101
|
+
}
|
|
9102
|
+
}
|
|
8454
9103
|
// Build table header
|
|
8455
9104
|
const header = visibleColumns.map((col) => col.label);
|
|
8456
9105
|
const separator = visibleColumns.map(() => "---");
|
|
8457
9106
|
// Build table rows
|
|
8458
|
-
const rows = topStrategies.map((result, index) => visibleColumns.map((col) => col.format(result, index)));
|
|
9107
|
+
const rows = await Promise.all(topStrategies.map(async (result, index) => Promise.all(visibleColumns.map((col) => col.format(result, index)))));
|
|
8459
9108
|
const tableData = [header, separator, ...rows];
|
|
8460
9109
|
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8461
9110
|
}
|
|
@@ -8463,9 +9112,10 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8463
9112
|
* Generates PNL table showing all closed signals across all strategies (View).
|
|
8464
9113
|
* Collects all signals from all strategies and formats as markdown table.
|
|
8465
9114
|
*
|
|
9115
|
+
* @param columns - Column configuration for formatting the PNL table
|
|
8466
9116
|
* @returns Markdown formatted PNL table
|
|
8467
9117
|
*/
|
|
8468
|
-
getPnlTable() {
|
|
9118
|
+
async getPnlTable(columns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
8469
9119
|
if (this._strategyResults.length === 0) {
|
|
8470
9120
|
return "No strategy results available.";
|
|
8471
9121
|
}
|
|
@@ -8489,11 +9139,16 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8489
9139
|
return "No closed signals available.";
|
|
8490
9140
|
}
|
|
8491
9141
|
// Build table header
|
|
8492
|
-
const visibleColumns =
|
|
9142
|
+
const visibleColumns = [];
|
|
9143
|
+
for (const col of columns) {
|
|
9144
|
+
if (await col.isVisible()) {
|
|
9145
|
+
visibleColumns.push(col);
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
8493
9148
|
const header = visibleColumns.map((col) => col.label);
|
|
8494
9149
|
const separator = visibleColumns.map(() => "---");
|
|
8495
9150
|
// Build table rows
|
|
8496
|
-
const rows = allSignals.map((signal) => visibleColumns.map((col) => col.format(signal)));
|
|
9151
|
+
const rows = await Promise.all(allSignals.map(async (signal, index) => Promise.all(visibleColumns.map((col) => col.format(signal, index)))));
|
|
8497
9152
|
const tableData = [header, separator, ...rows];
|
|
8498
9153
|
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8499
9154
|
}
|
|
@@ -8504,9 +9159,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8504
9159
|
* @param symbol - Trading symbol
|
|
8505
9160
|
* @param metric - Metric being optimized
|
|
8506
9161
|
* @param context - Context with exchangeName and frameName
|
|
9162
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9163
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8507
9164
|
* @returns Markdown formatted report with all results
|
|
8508
9165
|
*/
|
|
8509
|
-
async getReport(symbol, metric, context) {
|
|
9166
|
+
async getReport(symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
8510
9167
|
const results = await this.getData(symbol, metric, context);
|
|
8511
9168
|
// Get total signals for best strategy
|
|
8512
9169
|
const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
|
|
@@ -8526,11 +9183,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8526
9183
|
"",
|
|
8527
9184
|
"## Top Strategies Comparison",
|
|
8528
9185
|
"",
|
|
8529
|
-
this.getComparisonTable(
|
|
9186
|
+
await this.getComparisonTable(10, strategyColumns),
|
|
8530
9187
|
"",
|
|
8531
9188
|
"## All Signals (PNL Table)",
|
|
8532
9189
|
"",
|
|
8533
|
-
this.getPnlTable(),
|
|
9190
|
+
await this.getPnlTable(pnlColumns),
|
|
8534
9191
|
"",
|
|
8535
9192
|
"**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
|
|
8536
9193
|
].join("\n");
|
|
@@ -8542,9 +9199,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8542
9199
|
* @param metric - Metric being optimized
|
|
8543
9200
|
* @param context - Context with exchangeName and frameName
|
|
8544
9201
|
* @param path - Directory path to save report (default: "./dump/walker")
|
|
9202
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9203
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8545
9204
|
*/
|
|
8546
|
-
async dump(symbol, metric, context, path$1 = "./dump/walker") {
|
|
8547
|
-
const markdown = await this.getReport(symbol, metric, context);
|
|
9205
|
+
async dump(symbol, metric, context, path$1 = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
9206
|
+
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
8548
9207
|
try {
|
|
8549
9208
|
const dir = path.join(process.cwd(), path$1);
|
|
8550
9209
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -8637,6 +9296,8 @@ class WalkerMarkdownService {
|
|
|
8637
9296
|
* @param symbol - Trading symbol
|
|
8638
9297
|
* @param metric - Metric being optimized
|
|
8639
9298
|
* @param context - Context with exchangeName and frameName
|
|
9299
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9300
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8640
9301
|
* @returns Markdown formatted report string
|
|
8641
9302
|
*
|
|
8642
9303
|
* @example
|
|
@@ -8646,7 +9307,7 @@ class WalkerMarkdownService {
|
|
|
8646
9307
|
* console.log(markdown);
|
|
8647
9308
|
* ```
|
|
8648
9309
|
*/
|
|
8649
|
-
this.getReport = async (walkerName, symbol, metric, context) => {
|
|
9310
|
+
this.getReport = async (walkerName, symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
|
|
8650
9311
|
this.loggerService.log("walkerMarkdownService getReport", {
|
|
8651
9312
|
walkerName,
|
|
8652
9313
|
symbol,
|
|
@@ -8654,7 +9315,7 @@ class WalkerMarkdownService {
|
|
|
8654
9315
|
context,
|
|
8655
9316
|
});
|
|
8656
9317
|
const storage = this.getStorage(walkerName);
|
|
8657
|
-
return storage.getReport(symbol, metric, context);
|
|
9318
|
+
return storage.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
8658
9319
|
};
|
|
8659
9320
|
/**
|
|
8660
9321
|
* Saves walker report to disk.
|
|
@@ -8666,6 +9327,8 @@ class WalkerMarkdownService {
|
|
|
8666
9327
|
* @param metric - Metric being optimized
|
|
8667
9328
|
* @param context - Context with exchangeName and frameName
|
|
8668
9329
|
* @param path - Directory path to save report (default: "./dump/walker")
|
|
9330
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9331
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8669
9332
|
*
|
|
8670
9333
|
* @example
|
|
8671
9334
|
* ```typescript
|
|
@@ -8678,7 +9341,7 @@ class WalkerMarkdownService {
|
|
|
8678
9341
|
* await service.dump("my-walker", "BTCUSDT", "sharpeRatio", { exchangeName: "binance", frameName: "1d" }, "./custom/path");
|
|
8679
9342
|
* ```
|
|
8680
9343
|
*/
|
|
8681
|
-
this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker") => {
|
|
9344
|
+
this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
|
|
8682
9345
|
this.loggerService.log("walkerMarkdownService dump", {
|
|
8683
9346
|
walkerName,
|
|
8684
9347
|
symbol,
|
|
@@ -8687,7 +9350,7 @@ class WalkerMarkdownService {
|
|
|
8687
9350
|
path,
|
|
8688
9351
|
});
|
|
8689
9352
|
const storage = this.getStorage(walkerName);
|
|
8690
|
-
await storage.dump(symbol, metric, context, path);
|
|
9353
|
+
await storage.dump(symbol, metric, context, path, strategyColumns, pnlColumns);
|
|
8691
9354
|
};
|
|
8692
9355
|
/**
|
|
8693
9356
|
* Clears accumulated result data from storage.
|
|
@@ -8753,80 +9416,6 @@ function isUnsafe(value) {
|
|
|
8753
9416
|
}
|
|
8754
9417
|
return false;
|
|
8755
9418
|
}
|
|
8756
|
-
const columns$2 = [
|
|
8757
|
-
{
|
|
8758
|
-
key: "symbol",
|
|
8759
|
-
label: "Symbol",
|
|
8760
|
-
format: (data) => data.symbol,
|
|
8761
|
-
isVisible: () => true,
|
|
8762
|
-
},
|
|
8763
|
-
{
|
|
8764
|
-
key: "totalPnl",
|
|
8765
|
-
label: "Total PNL",
|
|
8766
|
-
format: (data) => data.totalPnl !== null ? functoolsKit.str(data.totalPnl, "%+.2f%%") : "N/A",
|
|
8767
|
-
isVisible: () => true,
|
|
8768
|
-
},
|
|
8769
|
-
{
|
|
8770
|
-
key: "sharpeRatio",
|
|
8771
|
-
label: "Sharpe",
|
|
8772
|
-
format: (data) => data.sharpeRatio !== null ? functoolsKit.str(data.sharpeRatio, "%.2f") : "N/A",
|
|
8773
|
-
isVisible: () => true,
|
|
8774
|
-
},
|
|
8775
|
-
{
|
|
8776
|
-
key: "profitFactor",
|
|
8777
|
-
label: "PF",
|
|
8778
|
-
format: (data) => data.profitFactor !== null ? functoolsKit.str(data.profitFactor, "%.2f") : "N/A",
|
|
8779
|
-
isVisible: () => true,
|
|
8780
|
-
},
|
|
8781
|
-
{
|
|
8782
|
-
key: "expectancy",
|
|
8783
|
-
label: "Expect",
|
|
8784
|
-
format: (data) => data.expectancy !== null ? functoolsKit.str(data.expectancy, "%+.2f%%") : "N/A",
|
|
8785
|
-
isVisible: () => true,
|
|
8786
|
-
},
|
|
8787
|
-
{
|
|
8788
|
-
key: "winRate",
|
|
8789
|
-
label: "WR",
|
|
8790
|
-
format: (data) => data.winRate !== null ? functoolsKit.str(data.winRate, "%.1f%%") : "N/A",
|
|
8791
|
-
isVisible: () => true,
|
|
8792
|
-
},
|
|
8793
|
-
{
|
|
8794
|
-
key: "avgWin",
|
|
8795
|
-
label: "Avg Win",
|
|
8796
|
-
format: (data) => data.avgWin !== null ? functoolsKit.str(data.avgWin, "%+.2f%%") : "N/A",
|
|
8797
|
-
isVisible: () => true,
|
|
8798
|
-
},
|
|
8799
|
-
{
|
|
8800
|
-
key: "avgLoss",
|
|
8801
|
-
label: "Avg Loss",
|
|
8802
|
-
format: (data) => data.avgLoss !== null ? functoolsKit.str(data.avgLoss, "%+.2f%%") : "N/A",
|
|
8803
|
-
isVisible: () => true,
|
|
8804
|
-
},
|
|
8805
|
-
{
|
|
8806
|
-
key: "maxDrawdown",
|
|
8807
|
-
label: "Max DD",
|
|
8808
|
-
format: (data) => data.maxDrawdown !== null ? functoolsKit.str(-data.maxDrawdown, "%.2f%%") : "N/A",
|
|
8809
|
-
isVisible: () => true,
|
|
8810
|
-
},
|
|
8811
|
-
{
|
|
8812
|
-
key: "maxWinStreak",
|
|
8813
|
-
label: "W Streak",
|
|
8814
|
-
format: (data) => data.maxWinStreak.toString(),
|
|
8815
|
-
isVisible: () => true,
|
|
8816
|
-
},
|
|
8817
|
-
{
|
|
8818
|
-
key: "maxLossStreak",
|
|
8819
|
-
label: "L Streak",
|
|
8820
|
-
format: (data) => data.maxLossStreak.toString(),
|
|
8821
|
-
isVisible: () => true,
|
|
8822
|
-
},
|
|
8823
|
-
{
|
|
8824
|
-
key: "totalTrades",
|
|
8825
|
-
label: "Trades",
|
|
8826
|
-
format: (data) => data.totalTrades.toString(),
|
|
8827
|
-
isVisible: () => true,
|
|
8828
|
-
},
|
|
8829
|
-
];
|
|
8830
9419
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
8831
9420
|
const MAX_EVENTS$2 = 250;
|
|
8832
9421
|
/**
|
|
@@ -9061,9 +9650,10 @@ class HeatmapStorage {
|
|
|
9061
9650
|
* Generates markdown report with portfolio heatmap table (View).
|
|
9062
9651
|
*
|
|
9063
9652
|
* @param strategyName - Strategy name for report title
|
|
9653
|
+
* @param columns - Column configuration for formatting the table
|
|
9064
9654
|
* @returns Promise resolving to markdown formatted report string
|
|
9065
9655
|
*/
|
|
9066
|
-
async getReport(strategyName) {
|
|
9656
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.heat_columns) {
|
|
9067
9657
|
const data = await this.getData();
|
|
9068
9658
|
if (data.symbols.length === 0) {
|
|
9069
9659
|
return [
|
|
@@ -9072,10 +9662,15 @@ class HeatmapStorage {
|
|
|
9072
9662
|
"*No data available*"
|
|
9073
9663
|
].join("\n");
|
|
9074
9664
|
}
|
|
9075
|
-
const visibleColumns =
|
|
9665
|
+
const visibleColumns = [];
|
|
9666
|
+
for (const col of columns) {
|
|
9667
|
+
if (await col.isVisible()) {
|
|
9668
|
+
visibleColumns.push(col);
|
|
9669
|
+
}
|
|
9670
|
+
}
|
|
9076
9671
|
const header = visibleColumns.map((col) => col.label);
|
|
9077
9672
|
const separator = visibleColumns.map(() => "---");
|
|
9078
|
-
const rows = data.symbols.map((row) => visibleColumns.map((col) => col.format(row)));
|
|
9673
|
+
const rows = await Promise.all(data.symbols.map(async (row, index) => Promise.all(visibleColumns.map((col) => col.format(row, index)))));
|
|
9079
9674
|
const tableData = [header, separator, ...rows];
|
|
9080
9675
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
9081
9676
|
return [
|
|
@@ -9091,9 +9686,10 @@ class HeatmapStorage {
|
|
|
9091
9686
|
*
|
|
9092
9687
|
* @param strategyName - Strategy name for filename
|
|
9093
9688
|
* @param path - Directory path to save report (default: "./dump/heatmap")
|
|
9689
|
+
* @param columns - Column configuration for formatting the table
|
|
9094
9690
|
*/
|
|
9095
|
-
async dump(strategyName, path$1 = "./dump/heatmap") {
|
|
9096
|
-
const markdown = await this.getReport(strategyName);
|
|
9691
|
+
async dump(strategyName, path$1 = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
9692
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
9097
9693
|
try {
|
|
9098
9694
|
const dir = path.join(process.cwd(), path$1);
|
|
9099
9695
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -9190,6 +9786,7 @@ class HeatMarkdownService {
|
|
|
9190
9786
|
* Generates markdown report with portfolio heatmap table for a strategy.
|
|
9191
9787
|
*
|
|
9192
9788
|
* @param strategyName - Strategy name to generate heatmap report for
|
|
9789
|
+
* @param columns - Column configuration for formatting the table
|
|
9193
9790
|
* @returns Promise resolving to markdown formatted report string
|
|
9194
9791
|
*
|
|
9195
9792
|
* @example
|
|
@@ -9209,12 +9806,12 @@ class HeatMarkdownService {
|
|
|
9209
9806
|
* // ...
|
|
9210
9807
|
* ```
|
|
9211
9808
|
*/
|
|
9212
|
-
this.getReport = async (strategyName) => {
|
|
9809
|
+
this.getReport = async (strategyName, columns = COLUMN_CONFIG.heat_columns) => {
|
|
9213
9810
|
this.loggerService.log(HEATMAP_METHOD_NAME_GET_REPORT, {
|
|
9214
9811
|
strategyName,
|
|
9215
9812
|
});
|
|
9216
9813
|
const storage = this.getStorage(strategyName);
|
|
9217
|
-
return storage.getReport(strategyName);
|
|
9814
|
+
return storage.getReport(strategyName, columns);
|
|
9218
9815
|
};
|
|
9219
9816
|
/**
|
|
9220
9817
|
* Saves heatmap report to disk for a strategy.
|
|
@@ -9224,6 +9821,7 @@ class HeatMarkdownService {
|
|
|
9224
9821
|
*
|
|
9225
9822
|
* @param strategyName - Strategy name to save heatmap report for
|
|
9226
9823
|
* @param path - Optional directory path to save report (default: "./dump/heatmap")
|
|
9824
|
+
* @param columns - Column configuration for formatting the table
|
|
9227
9825
|
*
|
|
9228
9826
|
* @example
|
|
9229
9827
|
* ```typescript
|
|
@@ -9236,13 +9834,13 @@ class HeatMarkdownService {
|
|
|
9236
9834
|
* await service.dump("my-strategy", "./reports");
|
|
9237
9835
|
* ```
|
|
9238
9836
|
*/
|
|
9239
|
-
this.dump = async (strategyName, path = "./dump/heatmap") => {
|
|
9837
|
+
this.dump = async (strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) => {
|
|
9240
9838
|
this.loggerService.log(HEATMAP_METHOD_NAME_DUMP, {
|
|
9241
9839
|
strategyName,
|
|
9242
9840
|
path,
|
|
9243
9841
|
});
|
|
9244
9842
|
const storage = this.getStorage(strategyName);
|
|
9245
|
-
await storage.dump(strategyName, path);
|
|
9843
|
+
await storage.dump(strategyName, path, columns);
|
|
9246
9844
|
};
|
|
9247
9845
|
/**
|
|
9248
9846
|
* Clears accumulated heatmap data from storage.
|
|
@@ -11096,6 +11694,9 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
|
|
|
11096
11694
|
if (self._states === NEED_FETCH) {
|
|
11097
11695
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11098
11696
|
}
|
|
11697
|
+
if (data.id !== self.params.signalId) {
|
|
11698
|
+
throw new Error(`Signal ID mismatch: expected ${self.params.signalId}, got ${data.id}`);
|
|
11699
|
+
}
|
|
11099
11700
|
let state = self._states.get(data.id);
|
|
11100
11701
|
if (!state) {
|
|
11101
11702
|
state = {
|
|
@@ -11120,7 +11721,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
|
|
|
11120
11721
|
}
|
|
11121
11722
|
}
|
|
11122
11723
|
if (shouldPersist) {
|
|
11123
|
-
await self._persistState(symbol,
|
|
11724
|
+
await self._persistState(symbol, data.strategyName);
|
|
11124
11725
|
}
|
|
11125
11726
|
};
|
|
11126
11727
|
/**
|
|
@@ -11142,6 +11743,9 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
11142
11743
|
if (self._states === NEED_FETCH) {
|
|
11143
11744
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11144
11745
|
}
|
|
11746
|
+
if (data.id !== self.params.signalId) {
|
|
11747
|
+
throw new Error(`Signal ID mismatch: expected ${self.params.signalId}, got ${data.id}`);
|
|
11748
|
+
}
|
|
11145
11749
|
let state = self._states.get(data.id);
|
|
11146
11750
|
if (!state) {
|
|
11147
11751
|
state = {
|
|
@@ -11167,7 +11771,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
11167
11771
|
}
|
|
11168
11772
|
}
|
|
11169
11773
|
if (shouldPersist) {
|
|
11170
|
-
await self._persistState(symbol,
|
|
11774
|
+
await self._persistState(symbol, data.strategyName);
|
|
11171
11775
|
}
|
|
11172
11776
|
};
|
|
11173
11777
|
/**
|
|
@@ -11176,15 +11780,29 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
11176
11780
|
* Loads persisted partial state from disk and restores in-memory Maps.
|
|
11177
11781
|
* Converts serialized arrays back to Sets for O(1) lookups.
|
|
11178
11782
|
*
|
|
11783
|
+
* ONLY runs in LIVE mode (backtest=false). In backtest mode, state is not persisted.
|
|
11784
|
+
*
|
|
11179
11785
|
* @param symbol - Trading pair symbol
|
|
11786
|
+
* @param strategyName - Strategy identifier
|
|
11787
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
11180
11788
|
* @param self - ClientPartial instance reference
|
|
11181
11789
|
*/
|
|
11182
|
-
const WAIT_FOR_INIT_FN = async (symbol, self) => {
|
|
11183
|
-
self.params.logger.debug("ClientPartial waitForInit", {
|
|
11184
|
-
|
|
11185
|
-
|
|
11790
|
+
const WAIT_FOR_INIT_FN = async (symbol, strategyName, self) => {
|
|
11791
|
+
self.params.logger.debug("ClientPartial waitForInit", {
|
|
11792
|
+
symbol,
|
|
11793
|
+
strategyName,
|
|
11794
|
+
backtest: self.params.backtest
|
|
11795
|
+
});
|
|
11796
|
+
if (self._states !== NEED_FETCH) {
|
|
11797
|
+
throw new Error("ClientPartial WAIT_FOR_INIT_FN should be called once!");
|
|
11798
|
+
}
|
|
11799
|
+
self._states = new Map();
|
|
11800
|
+
// Skip persistence in backtest mode
|
|
11801
|
+
if (self.params.backtest) {
|
|
11802
|
+
self.params.logger.debug("ClientPartial waitForInit: skipping persist read in backtest mode");
|
|
11803
|
+
return;
|
|
11186
11804
|
}
|
|
11187
|
-
const partialData = await PersistPartialAdapter.readPartialData(symbol);
|
|
11805
|
+
const partialData = await PersistPartialAdapter.readPartialData(symbol, strategyName);
|
|
11188
11806
|
for (const [signalId, data] of Object.entries(partialData)) {
|
|
11189
11807
|
const state = {
|
|
11190
11808
|
profitLevels: new Set(data.profitLevels),
|
|
@@ -11194,6 +11812,7 @@ const WAIT_FOR_INIT_FN = async (symbol, self) => {
|
|
|
11194
11812
|
}
|
|
11195
11813
|
self.params.logger.info("ClientPartial restored state", {
|
|
11196
11814
|
symbol,
|
|
11815
|
+
strategyName,
|
|
11197
11816
|
signalCount: Object.keys(partialData).length,
|
|
11198
11817
|
});
|
|
11199
11818
|
};
|
|
@@ -11272,23 +11891,24 @@ class ClientPartial {
|
|
|
11272
11891
|
/**
|
|
11273
11892
|
* Initializes partial state by loading from disk.
|
|
11274
11893
|
*
|
|
11275
|
-
* Uses singleshot pattern to ensure initialization happens exactly once per symbol.
|
|
11894
|
+
* Uses singleshot pattern to ensure initialization happens exactly once per symbol:strategyName.
|
|
11276
11895
|
* Reads persisted state from PersistPartialAdapter and restores to _states Map.
|
|
11277
11896
|
*
|
|
11278
11897
|
* Must be called before profit()/loss()/clear() methods.
|
|
11279
11898
|
*
|
|
11280
11899
|
* @param symbol - Trading pair symbol
|
|
11900
|
+
* @param strategyName - Strategy identifier
|
|
11901
|
+
* @param backtest - True if backtest mode, false if live mode
|
|
11281
11902
|
* @returns Promise that resolves when initialization is complete
|
|
11282
11903
|
*
|
|
11283
11904
|
* @example
|
|
11284
11905
|
* ```typescript
|
|
11285
11906
|
* const partial = new ClientPartial(params);
|
|
11286
|
-
* await partial.waitForInit("BTCUSDT"); // Load persisted state
|
|
11907
|
+
* await partial.waitForInit("BTCUSDT", "my-strategy", false); // Load persisted state (live mode)
|
|
11287
11908
|
* // Now profit()/loss() can be called
|
|
11288
11909
|
* ```
|
|
11289
11910
|
*/
|
|
11290
|
-
this.waitForInit = functoolsKit.singleshot(async (symbol) => await WAIT_FOR_INIT_FN(symbol, this));
|
|
11291
|
-
this._states = new Map();
|
|
11911
|
+
this.waitForInit = functoolsKit.singleshot(async (symbol, strategyName) => await WAIT_FOR_INIT_FN(symbol, strategyName, this));
|
|
11292
11912
|
}
|
|
11293
11913
|
/**
|
|
11294
11914
|
* Persists current partial state to disk.
|
|
@@ -11301,13 +11921,15 @@ class ClientPartial {
|
|
|
11301
11921
|
* Uses atomic file writes via PersistPartialAdapter.
|
|
11302
11922
|
*
|
|
11303
11923
|
* @param symbol - Trading pair symbol
|
|
11924
|
+
* @param strategyName - Strategy identifier
|
|
11925
|
+
* @param backtest - True if backtest mode
|
|
11304
11926
|
* @returns Promise that resolves when persistence is complete
|
|
11305
11927
|
*/
|
|
11306
|
-
async _persistState(symbol,
|
|
11307
|
-
if (backtest) {
|
|
11928
|
+
async _persistState(symbol, strategyName) {
|
|
11929
|
+
if (this.params.backtest) {
|
|
11308
11930
|
return;
|
|
11309
11931
|
}
|
|
11310
|
-
this.params.logger.debug("ClientPartial persistState", { symbol });
|
|
11932
|
+
this.params.logger.debug("ClientPartial persistState", { symbol, strategyName });
|
|
11311
11933
|
if (this._states === NEED_FETCH) {
|
|
11312
11934
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11313
11935
|
}
|
|
@@ -11318,7 +11940,7 @@ class ClientPartial {
|
|
|
11318
11940
|
lossLevels: Array.from(state.lossLevels),
|
|
11319
11941
|
};
|
|
11320
11942
|
}
|
|
11321
|
-
await PersistPartialAdapter.writePartialData(partialData, symbol);
|
|
11943
|
+
await PersistPartialAdapter.writePartialData(partialData, symbol, strategyName);
|
|
11322
11944
|
}
|
|
11323
11945
|
/**
|
|
11324
11946
|
* Processes profit state and emits events for newly reached profit levels.
|
|
@@ -11439,8 +12061,11 @@ class ClientPartial {
|
|
|
11439
12061
|
if (this._states === NEED_FETCH) {
|
|
11440
12062
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
11441
12063
|
}
|
|
12064
|
+
if (data.id !== this.params.signalId) {
|
|
12065
|
+
throw new Error(`Signal ID mismatch: expected ${this.params.signalId}, got ${data.id}`);
|
|
12066
|
+
}
|
|
11442
12067
|
this._states.delete(data.id);
|
|
11443
|
-
await this._persistState(symbol,
|
|
12068
|
+
await this._persistState(symbol, data.strategyName);
|
|
11444
12069
|
}
|
|
11445
12070
|
}
|
|
11446
12071
|
|
|
@@ -11535,15 +12160,17 @@ class PartialConnectionService {
|
|
|
11535
12160
|
/**
|
|
11536
12161
|
* Memoized factory function for ClientPartial instances.
|
|
11537
12162
|
*
|
|
11538
|
-
* Creates one ClientPartial per signal ID with configured callbacks.
|
|
12163
|
+
* Creates one ClientPartial per signal ID and backtest mode with configured callbacks.
|
|
11539
12164
|
* Instances are cached until clear() is called.
|
|
11540
12165
|
*
|
|
11541
|
-
* Key format: signalId
|
|
12166
|
+
* Key format: "signalId:backtest" or "signalId:live"
|
|
11542
12167
|
* Value: ClientPartial instance with logger and event emitters
|
|
11543
12168
|
*/
|
|
11544
|
-
this.getPartial = functoolsKit.memoize(([signalId]) => `${signalId}`, () => {
|
|
12169
|
+
this.getPartial = functoolsKit.memoize(([signalId, backtest]) => `${signalId}:${backtest ? "backtest" : "live"}`, (signalId, backtest) => {
|
|
11545
12170
|
return new ClientPartial({
|
|
12171
|
+
signalId,
|
|
11546
12172
|
logger: this.loggerService,
|
|
12173
|
+
backtest,
|
|
11547
12174
|
onProfit: COMMIT_PROFIT_FN,
|
|
11548
12175
|
onLoss: COMMIT_LOSS_FN,
|
|
11549
12176
|
});
|
|
@@ -11571,8 +12198,8 @@ class PartialConnectionService {
|
|
|
11571
12198
|
backtest,
|
|
11572
12199
|
when,
|
|
11573
12200
|
});
|
|
11574
|
-
const partial = this.getPartial(data.id);
|
|
11575
|
-
await partial.waitForInit(symbol);
|
|
12201
|
+
const partial = this.getPartial(data.id, backtest);
|
|
12202
|
+
await partial.waitForInit(symbol, data.strategyName);
|
|
11576
12203
|
return await partial.profit(symbol, data, currentPrice, revenuePercent, backtest, when);
|
|
11577
12204
|
};
|
|
11578
12205
|
/**
|
|
@@ -11598,8 +12225,8 @@ class PartialConnectionService {
|
|
|
11598
12225
|
backtest,
|
|
11599
12226
|
when,
|
|
11600
12227
|
});
|
|
11601
|
-
const partial = this.getPartial(data.id);
|
|
11602
|
-
await partial.waitForInit(symbol);
|
|
12228
|
+
const partial = this.getPartial(data.id, backtest);
|
|
12229
|
+
await partial.waitForInit(symbol, data.strategyName);
|
|
11603
12230
|
return await partial.loss(symbol, data, currentPrice, lossPercent, backtest, when);
|
|
11604
12231
|
};
|
|
11605
12232
|
/**
|
|
@@ -11620,75 +12247,21 @@ class PartialConnectionService {
|
|
|
11620
12247
|
* @returns Promise that resolves when clear is complete
|
|
11621
12248
|
*/
|
|
11622
12249
|
this.clear = async (symbol, data, priceClose, backtest) => {
|
|
11623
|
-
this.loggerService.log("partialConnectionService
|
|
12250
|
+
this.loggerService.log("partialConnectionService clear", {
|
|
11624
12251
|
symbol,
|
|
11625
12252
|
data,
|
|
11626
12253
|
priceClose,
|
|
12254
|
+
backtest,
|
|
11627
12255
|
});
|
|
11628
|
-
const partial = this.getPartial(data.id);
|
|
11629
|
-
await partial.waitForInit(symbol);
|
|
12256
|
+
const partial = this.getPartial(data.id, backtest);
|
|
12257
|
+
await partial.waitForInit(symbol, data.strategyName);
|
|
11630
12258
|
await partial.clear(symbol, data, priceClose, backtest);
|
|
11631
|
-
|
|
12259
|
+
const key = `${data.id}:${backtest ? "backtest" : "live"}`;
|
|
12260
|
+
this.getPartial.clear(key);
|
|
11632
12261
|
};
|
|
11633
12262
|
}
|
|
11634
12263
|
}
|
|
11635
12264
|
|
|
11636
|
-
const columns$1 = [
|
|
11637
|
-
{
|
|
11638
|
-
key: "action",
|
|
11639
|
-
label: "Action",
|
|
11640
|
-
format: (data) => data.action.toUpperCase(),
|
|
11641
|
-
isVisible: () => true,
|
|
11642
|
-
},
|
|
11643
|
-
{
|
|
11644
|
-
key: "symbol",
|
|
11645
|
-
label: "Symbol",
|
|
11646
|
-
format: (data) => data.symbol,
|
|
11647
|
-
isVisible: () => true,
|
|
11648
|
-
},
|
|
11649
|
-
{
|
|
11650
|
-
key: "strategyName",
|
|
11651
|
-
label: "Strategy",
|
|
11652
|
-
format: (data) => data.strategyName,
|
|
11653
|
-
isVisible: () => true,
|
|
11654
|
-
},
|
|
11655
|
-
{
|
|
11656
|
-
key: "signalId",
|
|
11657
|
-
label: "Signal ID",
|
|
11658
|
-
format: (data) => data.signalId,
|
|
11659
|
-
isVisible: () => true,
|
|
11660
|
-
},
|
|
11661
|
-
{
|
|
11662
|
-
key: "position",
|
|
11663
|
-
label: "Position",
|
|
11664
|
-
format: (data) => data.position.toUpperCase(),
|
|
11665
|
-
isVisible: () => true,
|
|
11666
|
-
},
|
|
11667
|
-
{
|
|
11668
|
-
key: "level",
|
|
11669
|
-
label: "Level %",
|
|
11670
|
-
format: (data) => data.action === "profit" ? `+${data.level}%` : `-${data.level}%`,
|
|
11671
|
-
isVisible: () => true,
|
|
11672
|
-
},
|
|
11673
|
-
{
|
|
11674
|
-
key: "currentPrice",
|
|
11675
|
-
label: "Current Price",
|
|
11676
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
11677
|
-
isVisible: () => true,
|
|
11678
|
-
},
|
|
11679
|
-
{
|
|
11680
|
-
key: "timestamp",
|
|
11681
|
-
label: "Timestamp",
|
|
11682
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
11683
|
-
isVisible: () => true,
|
|
11684
|
-
},
|
|
11685
|
-
{
|
|
11686
|
-
key: "mode",
|
|
11687
|
-
label: "Mode",
|
|
11688
|
-
format: (data) => (data.backtest ? "Backtest" : "Live"),
|
|
11689
|
-
isVisible: () => true,
|
|
11690
|
-
},
|
|
11691
|
-
];
|
|
11692
12265
|
/** Maximum number of events to store in partial reports */
|
|
11693
12266
|
const MAX_EVENTS$1 = 250;
|
|
11694
12267
|
/**
|
|
@@ -11778,9 +12351,10 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11778
12351
|
*
|
|
11779
12352
|
* @param symbol - Trading pair symbol
|
|
11780
12353
|
* @param strategyName - Strategy name
|
|
12354
|
+
* @param columns - Column configuration for formatting the table
|
|
11781
12355
|
* @returns Markdown formatted report with all events
|
|
11782
12356
|
*/
|
|
11783
|
-
async getReport(symbol, strategyName) {
|
|
12357
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) {
|
|
11784
12358
|
const stats = await this.getData();
|
|
11785
12359
|
if (stats.totalEvents === 0) {
|
|
11786
12360
|
return [
|
|
@@ -11789,10 +12363,15 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11789
12363
|
"No partial profit/loss events recorded yet."
|
|
11790
12364
|
].join("\n");
|
|
11791
12365
|
}
|
|
11792
|
-
const visibleColumns =
|
|
12366
|
+
const visibleColumns = [];
|
|
12367
|
+
for (const col of columns) {
|
|
12368
|
+
if (await col.isVisible()) {
|
|
12369
|
+
visibleColumns.push(col);
|
|
12370
|
+
}
|
|
12371
|
+
}
|
|
11793
12372
|
const header = visibleColumns.map((col) => col.label);
|
|
11794
12373
|
const separator = visibleColumns.map(() => "---");
|
|
11795
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
12374
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
11796
12375
|
const tableData = [header, separator, ...rows];
|
|
11797
12376
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
11798
12377
|
return [
|
|
@@ -11811,9 +12390,10 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11811
12390
|
* @param symbol - Trading pair symbol
|
|
11812
12391
|
* @param strategyName - Strategy name
|
|
11813
12392
|
* @param path - Directory path to save report (default: "./dump/partial")
|
|
12393
|
+
* @param columns - Column configuration for formatting the table
|
|
11814
12394
|
*/
|
|
11815
|
-
async dump(symbol, strategyName, path$1 = "./dump/partial") {
|
|
11816
|
-
const markdown = await this.getReport(symbol, strategyName);
|
|
12395
|
+
async dump(symbol, strategyName, path$1 = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
12396
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
11817
12397
|
try {
|
|
11818
12398
|
const dir = path.join(process.cwd(), path$1);
|
|
11819
12399
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -11924,6 +12504,7 @@ class PartialMarkdownService {
|
|
|
11924
12504
|
*
|
|
11925
12505
|
* @param symbol - Trading pair symbol to generate report for
|
|
11926
12506
|
* @param strategyName - Strategy name to generate report for
|
|
12507
|
+
* @param columns - Column configuration for formatting the table
|
|
11927
12508
|
* @returns Markdown formatted report string with table of all events
|
|
11928
12509
|
*
|
|
11929
12510
|
* @example
|
|
@@ -11933,13 +12514,13 @@ class PartialMarkdownService {
|
|
|
11933
12514
|
* console.log(markdown);
|
|
11934
12515
|
* ```
|
|
11935
12516
|
*/
|
|
11936
|
-
this.getReport = async (symbol, strategyName) => {
|
|
12517
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) => {
|
|
11937
12518
|
this.loggerService.log("partialMarkdownService getReport", {
|
|
11938
12519
|
symbol,
|
|
11939
12520
|
strategyName,
|
|
11940
12521
|
});
|
|
11941
12522
|
const storage = this.getStorage(symbol, strategyName);
|
|
11942
|
-
return storage.getReport(symbol, strategyName);
|
|
12523
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
11943
12524
|
};
|
|
11944
12525
|
/**
|
|
11945
12526
|
* Saves symbol-strategy report to disk.
|
|
@@ -11949,6 +12530,7 @@ class PartialMarkdownService {
|
|
|
11949
12530
|
* @param symbol - Trading pair symbol to save report for
|
|
11950
12531
|
* @param strategyName - Strategy name to save report for
|
|
11951
12532
|
* @param path - Directory path to save report (default: "./dump/partial")
|
|
12533
|
+
* @param columns - Column configuration for formatting the table
|
|
11952
12534
|
*
|
|
11953
12535
|
* @example
|
|
11954
12536
|
* ```typescript
|
|
@@ -11961,14 +12543,14 @@ class PartialMarkdownService {
|
|
|
11961
12543
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
11962
12544
|
* ```
|
|
11963
12545
|
*/
|
|
11964
|
-
this.dump = async (symbol, strategyName, path = "./dump/partial") => {
|
|
12546
|
+
this.dump = async (symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) => {
|
|
11965
12547
|
this.loggerService.log("partialMarkdownService dump", {
|
|
11966
12548
|
symbol,
|
|
11967
12549
|
strategyName,
|
|
11968
12550
|
path,
|
|
11969
12551
|
});
|
|
11970
12552
|
const storage = this.getStorage(symbol, strategyName);
|
|
11971
|
-
await storage.dump(symbol, strategyName, path);
|
|
12553
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
11972
12554
|
};
|
|
11973
12555
|
/**
|
|
11974
12556
|
* Clears accumulated event data from storage.
|
|
@@ -12428,92 +13010,6 @@ class ConfigValidationService {
|
|
|
12428
13010
|
}
|
|
12429
13011
|
}
|
|
12430
13012
|
|
|
12431
|
-
const columns = [
|
|
12432
|
-
{
|
|
12433
|
-
key: "symbol",
|
|
12434
|
-
label: "Symbol",
|
|
12435
|
-
format: (data) => data.symbol,
|
|
12436
|
-
isVisible: () => true,
|
|
12437
|
-
},
|
|
12438
|
-
{
|
|
12439
|
-
key: "strategyName",
|
|
12440
|
-
label: "Strategy",
|
|
12441
|
-
format: (data) => data.strategyName,
|
|
12442
|
-
isVisible: () => true,
|
|
12443
|
-
},
|
|
12444
|
-
{
|
|
12445
|
-
key: "signalId",
|
|
12446
|
-
label: "Signal ID",
|
|
12447
|
-
format: (data) => data.pendingSignal.id || "N/A",
|
|
12448
|
-
isVisible: () => true,
|
|
12449
|
-
},
|
|
12450
|
-
{
|
|
12451
|
-
key: "position",
|
|
12452
|
-
label: "Position",
|
|
12453
|
-
format: (data) => data.pendingSignal.position.toUpperCase(),
|
|
12454
|
-
isVisible: () => true,
|
|
12455
|
-
},
|
|
12456
|
-
{
|
|
12457
|
-
key: "note",
|
|
12458
|
-
label: "Note",
|
|
12459
|
-
format: (data) => toPlainString(data.pendingSignal.note ?? "N/A"),
|
|
12460
|
-
isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
|
|
12461
|
-
},
|
|
12462
|
-
{
|
|
12463
|
-
key: "exchangeName",
|
|
12464
|
-
label: "Exchange",
|
|
12465
|
-
format: (data) => data.exchangeName,
|
|
12466
|
-
isVisible: () => true,
|
|
12467
|
-
},
|
|
12468
|
-
{
|
|
12469
|
-
key: "openPrice",
|
|
12470
|
-
label: "Open Price",
|
|
12471
|
-
format: (data) => data.pendingSignal.priceOpen !== undefined
|
|
12472
|
-
? `${data.pendingSignal.priceOpen.toFixed(8)} USD`
|
|
12473
|
-
: "N/A",
|
|
12474
|
-
isVisible: () => true,
|
|
12475
|
-
},
|
|
12476
|
-
{
|
|
12477
|
-
key: "takeProfit",
|
|
12478
|
-
label: "Take Profit",
|
|
12479
|
-
format: (data) => data.pendingSignal.priceTakeProfit !== undefined
|
|
12480
|
-
? `${data.pendingSignal.priceTakeProfit.toFixed(8)} USD`
|
|
12481
|
-
: "N/A",
|
|
12482
|
-
isVisible: () => true,
|
|
12483
|
-
},
|
|
12484
|
-
{
|
|
12485
|
-
key: "stopLoss",
|
|
12486
|
-
label: "Stop Loss",
|
|
12487
|
-
format: (data) => data.pendingSignal.priceStopLoss !== undefined
|
|
12488
|
-
? `${data.pendingSignal.priceStopLoss.toFixed(8)} USD`
|
|
12489
|
-
: "N/A",
|
|
12490
|
-
isVisible: () => true,
|
|
12491
|
-
},
|
|
12492
|
-
{
|
|
12493
|
-
key: "currentPrice",
|
|
12494
|
-
label: "Current Price",
|
|
12495
|
-
format: (data) => `${data.currentPrice.toFixed(8)} USD`,
|
|
12496
|
-
isVisible: () => true,
|
|
12497
|
-
},
|
|
12498
|
-
{
|
|
12499
|
-
key: "activePositionCount",
|
|
12500
|
-
label: "Active Positions",
|
|
12501
|
-
format: (data) => data.activePositionCount.toString(),
|
|
12502
|
-
isVisible: () => true,
|
|
12503
|
-
},
|
|
12504
|
-
{
|
|
12505
|
-
key: "comment",
|
|
12506
|
-
label: "Reason",
|
|
12507
|
-
format: (data) => data.comment,
|
|
12508
|
-
isVisible: () => true,
|
|
12509
|
-
},
|
|
12510
|
-
{
|
|
12511
|
-
key: "timestamp",
|
|
12512
|
-
label: "Timestamp",
|
|
12513
|
-
format: (data) => new Date(data.timestamp).toISOString(),
|
|
12514
|
-
isVisible: () => true,
|
|
12515
|
-
},
|
|
12516
|
-
];
|
|
12517
13013
|
/** Maximum number of events to store in risk reports */
|
|
12518
13014
|
const MAX_EVENTS = 250;
|
|
12519
13015
|
/**
|
|
@@ -12569,9 +13065,10 @@ class ReportStorage {
|
|
|
12569
13065
|
*
|
|
12570
13066
|
* @param symbol - Trading pair symbol
|
|
12571
13067
|
* @param strategyName - Strategy name
|
|
13068
|
+
* @param columns - Column configuration for formatting the table
|
|
12572
13069
|
* @returns Markdown formatted report with all events
|
|
12573
13070
|
*/
|
|
12574
|
-
async getReport(symbol, strategyName) {
|
|
13071
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) {
|
|
12575
13072
|
const stats = await this.getData();
|
|
12576
13073
|
if (stats.totalRejections === 0) {
|
|
12577
13074
|
return [
|
|
@@ -12580,10 +13077,15 @@ class ReportStorage {
|
|
|
12580
13077
|
"No risk rejections recorded yet.",
|
|
12581
13078
|
].join("\n");
|
|
12582
13079
|
}
|
|
12583
|
-
const visibleColumns =
|
|
13080
|
+
const visibleColumns = [];
|
|
13081
|
+
for (const col of columns) {
|
|
13082
|
+
if (await col.isVisible()) {
|
|
13083
|
+
visibleColumns.push(col);
|
|
13084
|
+
}
|
|
13085
|
+
}
|
|
12584
13086
|
const header = visibleColumns.map((col) => col.label);
|
|
12585
13087
|
const separator = visibleColumns.map(() => "---");
|
|
12586
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
13088
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
12587
13089
|
const tableData = [header, separator, ...rows];
|
|
12588
13090
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
12589
13091
|
return [
|
|
@@ -12606,9 +13108,10 @@ class ReportStorage {
|
|
|
12606
13108
|
* @param symbol - Trading pair symbol
|
|
12607
13109
|
* @param strategyName - Strategy name
|
|
12608
13110
|
* @param path - Directory path to save report (default: "./dump/risk")
|
|
13111
|
+
* @param columns - Column configuration for formatting the table
|
|
12609
13112
|
*/
|
|
12610
|
-
async dump(symbol, strategyName, path$1 = "./dump/risk") {
|
|
12611
|
-
const markdown = await this.getReport(symbol, strategyName);
|
|
13113
|
+
async dump(symbol, strategyName, path$1 = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
13114
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
12612
13115
|
try {
|
|
12613
13116
|
const dir = path.join(process.cwd(), path$1);
|
|
12614
13117
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -12700,6 +13203,7 @@ class RiskMarkdownService {
|
|
|
12700
13203
|
*
|
|
12701
13204
|
* @param symbol - Trading pair symbol to generate report for
|
|
12702
13205
|
* @param strategyName - Strategy name to generate report for
|
|
13206
|
+
* @param columns - Column configuration for formatting the table
|
|
12703
13207
|
* @returns Markdown formatted report string with table of all events
|
|
12704
13208
|
*
|
|
12705
13209
|
* @example
|
|
@@ -12709,13 +13213,13 @@ class RiskMarkdownService {
|
|
|
12709
13213
|
* console.log(markdown);
|
|
12710
13214
|
* ```
|
|
12711
13215
|
*/
|
|
12712
|
-
this.getReport = async (symbol, strategyName) => {
|
|
13216
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) => {
|
|
12713
13217
|
this.loggerService.log("riskMarkdownService getReport", {
|
|
12714
13218
|
symbol,
|
|
12715
13219
|
strategyName,
|
|
12716
13220
|
});
|
|
12717
13221
|
const storage = this.getStorage(symbol, strategyName);
|
|
12718
|
-
return storage.getReport(symbol, strategyName);
|
|
13222
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
12719
13223
|
};
|
|
12720
13224
|
/**
|
|
12721
13225
|
* Saves symbol-strategy report to disk.
|
|
@@ -12725,6 +13229,7 @@ class RiskMarkdownService {
|
|
|
12725
13229
|
* @param symbol - Trading pair symbol to save report for
|
|
12726
13230
|
* @param strategyName - Strategy name to save report for
|
|
12727
13231
|
* @param path - Directory path to save report (default: "./dump/risk")
|
|
13232
|
+
* @param columns - Column configuration for formatting the table
|
|
12728
13233
|
*
|
|
12729
13234
|
* @example
|
|
12730
13235
|
* ```typescript
|
|
@@ -12737,14 +13242,14 @@ class RiskMarkdownService {
|
|
|
12737
13242
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
12738
13243
|
* ```
|
|
12739
13244
|
*/
|
|
12740
|
-
this.dump = async (symbol, strategyName, path = "./dump/risk") => {
|
|
13245
|
+
this.dump = async (symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) => {
|
|
12741
13246
|
this.loggerService.log("riskMarkdownService dump", {
|
|
12742
13247
|
symbol,
|
|
12743
13248
|
strategyName,
|
|
12744
13249
|
path,
|
|
12745
13250
|
});
|
|
12746
13251
|
const storage = this.getStorage(symbol, strategyName);
|
|
12747
|
-
await storage.dump(symbol, strategyName, path);
|
|
13252
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
12748
13253
|
};
|
|
12749
13254
|
/**
|
|
12750
13255
|
* Clears accumulated event data from storage.
|
|
@@ -12794,6 +13299,118 @@ class RiskMarkdownService {
|
|
|
12794
13299
|
}
|
|
12795
13300
|
}
|
|
12796
13301
|
|
|
13302
|
+
/**
|
|
13303
|
+
* Service for validating column configurations to ensure consistency with ColumnModel interface
|
|
13304
|
+
* and prevent invalid column definitions.
|
|
13305
|
+
*
|
|
13306
|
+
* Performs comprehensive validation on all column definitions in COLUMN_CONFIG:
|
|
13307
|
+
* - **Required fields**: All columns must have key, label, format, and isVisible properties
|
|
13308
|
+
* - **Unique keys**: All key values must be unique within each column collection
|
|
13309
|
+
* - **Function validation**: format and isVisible must be callable functions
|
|
13310
|
+
* - **Data types**: key and label must be non-empty strings
|
|
13311
|
+
*
|
|
13312
|
+
* @throws {Error} If any validation fails, throws with detailed breakdown of all errors
|
|
13313
|
+
*
|
|
13314
|
+
* @example
|
|
13315
|
+
* ```typescript
|
|
13316
|
+
* const validator = new ColumnValidationService();
|
|
13317
|
+
* validator.validate(); // Throws if column configuration is invalid
|
|
13318
|
+
* ```
|
|
13319
|
+
*
|
|
13320
|
+
* @example Validation failure output:
|
|
13321
|
+
* ```
|
|
13322
|
+
* Column configuration validation failed:
|
|
13323
|
+
* 1. backtest_columns[0]: Missing required field "format"
|
|
13324
|
+
* 2. heat_columns: Duplicate key "symbol" at indexes 1, 5
|
|
13325
|
+
* 3. live_columns[3].isVisible must be a function, got "boolean"
|
|
13326
|
+
* ```
|
|
13327
|
+
*/
|
|
13328
|
+
class ColumnValidationService {
|
|
13329
|
+
constructor() {
|
|
13330
|
+
/**
|
|
13331
|
+
* @private
|
|
13332
|
+
* @readonly
|
|
13333
|
+
* Injected logger service instance
|
|
13334
|
+
*/
|
|
13335
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
13336
|
+
/**
|
|
13337
|
+
* Validates all column configurations in COLUMN_CONFIG for structural correctness.
|
|
13338
|
+
*
|
|
13339
|
+
* Checks:
|
|
13340
|
+
* 1. All required fields (key, label, format, isVisible) are present in each column
|
|
13341
|
+
* 2. key and label are non-empty strings
|
|
13342
|
+
* 3. format and isVisible are functions (not other types)
|
|
13343
|
+
* 4. All keys are unique within each column collection
|
|
13344
|
+
*
|
|
13345
|
+
* @throws Error if configuration is invalid
|
|
13346
|
+
*/
|
|
13347
|
+
this.validate = () => {
|
|
13348
|
+
this.loggerService.log("columnValidationService validate");
|
|
13349
|
+
const errors = [];
|
|
13350
|
+
// Iterate through all column collections in COLUMN_CONFIG
|
|
13351
|
+
for (const [configKey, columns] of Object.entries(COLUMN_CONFIG)) {
|
|
13352
|
+
if (!Array.isArray(columns)) {
|
|
13353
|
+
errors.push(`${configKey} is not an array, got ${typeof columns}`);
|
|
13354
|
+
continue;
|
|
13355
|
+
}
|
|
13356
|
+
// Track keys for uniqueness check
|
|
13357
|
+
const keyMap = new Map();
|
|
13358
|
+
// Validate each column in the collection
|
|
13359
|
+
columns.forEach((column, index) => {
|
|
13360
|
+
if (!column || typeof column !== "object") {
|
|
13361
|
+
errors.push(`${configKey}[${index}]: Column must be an object, got ${typeof column}`);
|
|
13362
|
+
return;
|
|
13363
|
+
}
|
|
13364
|
+
// Check for all required fields
|
|
13365
|
+
const requiredFields = ["key", "label", "format", "isVisible"];
|
|
13366
|
+
for (const field of requiredFields) {
|
|
13367
|
+
if (!(field in column)) {
|
|
13368
|
+
errors.push(`${configKey}[${index}]: Missing required field "${field}"`);
|
|
13369
|
+
}
|
|
13370
|
+
}
|
|
13371
|
+
// Validate key and label are non-empty strings
|
|
13372
|
+
if (typeof column.key !== "string" || column.key.trim() === "") {
|
|
13373
|
+
errors.push(`${configKey}[${index}].key must be a non-empty string, got ${typeof column.key === "string" ? `"${column.key}"` : typeof column.key}`);
|
|
13374
|
+
}
|
|
13375
|
+
else {
|
|
13376
|
+
// Track key for uniqueness check
|
|
13377
|
+
if (!keyMap.has(column.key)) {
|
|
13378
|
+
keyMap.set(column.key, []);
|
|
13379
|
+
}
|
|
13380
|
+
keyMap.get(column.key).push(index);
|
|
13381
|
+
}
|
|
13382
|
+
if (typeof column.label !== "string" || column.label.trim() === "") {
|
|
13383
|
+
errors.push(`${configKey}[${index}].label must be a non-empty string, got ${typeof column.label === "string" ? `"${column.label}"` : typeof column.label}`);
|
|
13384
|
+
}
|
|
13385
|
+
// Validate format is a function
|
|
13386
|
+
if (typeof column.format !== "function") {
|
|
13387
|
+
errors.push(`${configKey}[${index}].format must be a function, got "${typeof column.format}"`);
|
|
13388
|
+
}
|
|
13389
|
+
// Validate isVisible is a function
|
|
13390
|
+
if (typeof column.isVisible !== "function") {
|
|
13391
|
+
errors.push(`${configKey}[${index}].isVisible must be a function, got "${typeof column.isVisible}"`);
|
|
13392
|
+
}
|
|
13393
|
+
});
|
|
13394
|
+
// Check for duplicate keys
|
|
13395
|
+
for (const [key, indexes] of keyMap.entries()) {
|
|
13396
|
+
if (indexes.length > 1) {
|
|
13397
|
+
errors.push(`${configKey}: Duplicate key "${key}" at indexes ${indexes.join(", ")}`);
|
|
13398
|
+
}
|
|
13399
|
+
}
|
|
13400
|
+
}
|
|
13401
|
+
// Throw aggregated errors if any
|
|
13402
|
+
if (errors.length > 0) {
|
|
13403
|
+
const errorMessage = `Column configuration validation failed:\n${errors
|
|
13404
|
+
.map((e, i) => ` ${i + 1}. ${e}`)
|
|
13405
|
+
.join("\n")}`;
|
|
13406
|
+
this.loggerService.warn(errorMessage);
|
|
13407
|
+
throw new Error(errorMessage);
|
|
13408
|
+
}
|
|
13409
|
+
this.loggerService.log("columnValidationService validation passed");
|
|
13410
|
+
};
|
|
13411
|
+
}
|
|
13412
|
+
}
|
|
13413
|
+
|
|
12797
13414
|
{
|
|
12798
13415
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
12799
13416
|
}
|
|
@@ -12865,6 +13482,7 @@ class RiskMarkdownService {
|
|
|
12865
13482
|
provide(TYPES.riskValidationService, () => new RiskValidationService());
|
|
12866
13483
|
provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
|
|
12867
13484
|
provide(TYPES.configValidationService, () => new ConfigValidationService());
|
|
13485
|
+
provide(TYPES.columnValidationService, () => new ColumnValidationService());
|
|
12868
13486
|
}
|
|
12869
13487
|
{
|
|
12870
13488
|
provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
|
|
@@ -12941,6 +13559,7 @@ const validationServices = {
|
|
|
12941
13559
|
riskValidationService: inject(TYPES.riskValidationService),
|
|
12942
13560
|
optimizerValidationService: inject(TYPES.optimizerValidationService),
|
|
12943
13561
|
configValidationService: inject(TYPES.configValidationService),
|
|
13562
|
+
columnValidationService: inject(TYPES.columnValidationService),
|
|
12944
13563
|
};
|
|
12945
13564
|
const templateServices = {
|
|
12946
13565
|
optimizerTemplateService: inject(TYPES.optimizerTemplateService),
|
|
@@ -13040,6 +13659,77 @@ function getConfig() {
|
|
|
13040
13659
|
function getDefaultConfig() {
|
|
13041
13660
|
return DEFAULT_CONFIG;
|
|
13042
13661
|
}
|
|
13662
|
+
/**
|
|
13663
|
+
* Sets custom column configurations for markdown report generation.
|
|
13664
|
+
*
|
|
13665
|
+
* Allows overriding default column definitions for any report type.
|
|
13666
|
+
* All columns are validated before assignment to ensure structural correctness.
|
|
13667
|
+
*
|
|
13668
|
+
* @param columns - Partial column configuration object to override default column settings
|
|
13669
|
+
* @param _unsafe - Skip column validations - required for testbed
|
|
13670
|
+
*
|
|
13671
|
+
* @example
|
|
13672
|
+
* ```typescript
|
|
13673
|
+
* setColumns({
|
|
13674
|
+
* backtest_columns: [
|
|
13675
|
+
* {
|
|
13676
|
+
* key: "customId",
|
|
13677
|
+
* label: "Custom ID",
|
|
13678
|
+
* format: (data) => data.signal.id,
|
|
13679
|
+
* isVisible: () => true
|
|
13680
|
+
* }
|
|
13681
|
+
* ],
|
|
13682
|
+
* });
|
|
13683
|
+
* ```
|
|
13684
|
+
*
|
|
13685
|
+
* @throws {Error} If column configuration is invalid
|
|
13686
|
+
*/
|
|
13687
|
+
function setColumns(columns, _unsafe) {
|
|
13688
|
+
const prevConfig = Object.assign({}, COLUMN_CONFIG);
|
|
13689
|
+
try {
|
|
13690
|
+
Object.assign(COLUMN_CONFIG, columns);
|
|
13691
|
+
!_unsafe && backtest$1.columnValidationService.validate();
|
|
13692
|
+
}
|
|
13693
|
+
catch (error) {
|
|
13694
|
+
console.warn(`backtest-kit setColumns failed: ${functoolsKit.getErrorMessage(error)}`, columns);
|
|
13695
|
+
Object.assign(COLUMN_CONFIG, prevConfig);
|
|
13696
|
+
throw error;
|
|
13697
|
+
}
|
|
13698
|
+
}
|
|
13699
|
+
/**
|
|
13700
|
+
* Retrieves a copy of the current column configuration for markdown report generation.
|
|
13701
|
+
*
|
|
13702
|
+
* Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
|
|
13703
|
+
* Use this to inspect the current column definitions without modifying them.
|
|
13704
|
+
*
|
|
13705
|
+
* @returns {ColumnConfig} A copy of the current column configuration object
|
|
13706
|
+
*
|
|
13707
|
+
* @example
|
|
13708
|
+
* ```typescript
|
|
13709
|
+
* const currentColumns = getColumns();
|
|
13710
|
+
* console.log(currentColumns.backtest_columns.length);
|
|
13711
|
+
* ```
|
|
13712
|
+
*/
|
|
13713
|
+
function getColumns() {
|
|
13714
|
+
return Object.assign({}, COLUMN_CONFIG);
|
|
13715
|
+
}
|
|
13716
|
+
/**
|
|
13717
|
+
* Retrieves the default column configuration object for markdown report generation.
|
|
13718
|
+
*
|
|
13719
|
+
* Returns a reference to the default column definitions with all preset values.
|
|
13720
|
+
* Use this to see what column options are available and their default definitions.
|
|
13721
|
+
*
|
|
13722
|
+
* @returns {ColumnConfig} The default column configuration object
|
|
13723
|
+
*
|
|
13724
|
+
* @example
|
|
13725
|
+
* ```typescript
|
|
13726
|
+
* const defaultColumns = getDefaultColumns();
|
|
13727
|
+
* console.log(defaultColumns.backtest_columns);
|
|
13728
|
+
* ```
|
|
13729
|
+
*/
|
|
13730
|
+
function getDefaultColumns() {
|
|
13731
|
+
return DEFAULT_COLUMNS;
|
|
13732
|
+
}
|
|
13043
13733
|
|
|
13044
13734
|
const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
|
|
13045
13735
|
const ADD_EXCHANGE_METHOD_NAME = "add.addExchange";
|
|
@@ -14919,12 +15609,12 @@ class BacktestInstance {
|
|
|
14919
15609
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
14920
15610
|
}
|
|
14921
15611
|
{
|
|
14922
|
-
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
15612
|
+
backtest$1.strategyCoreService.clear(true, { symbol, strategyName: context.strategyName });
|
|
14923
15613
|
}
|
|
14924
15614
|
{
|
|
14925
15615
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
14926
|
-
riskName && backtest$1.riskGlobalService.clear(riskName);
|
|
14927
|
-
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(riskName));
|
|
15616
|
+
riskName && backtest$1.riskGlobalService.clear(true, riskName);
|
|
15617
|
+
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(true, riskName));
|
|
14928
15618
|
}
|
|
14929
15619
|
return backtest$1.backtestCommandService.run(symbol, context);
|
|
14930
15620
|
};
|
|
@@ -14955,9 +15645,9 @@ class BacktestInstance {
|
|
|
14955
15645
|
});
|
|
14956
15646
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
14957
15647
|
return () => {
|
|
14958
|
-
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }
|
|
15648
|
+
backtest$1.strategyCoreService.stop(true, { symbol, strategyName: context.strategyName });
|
|
14959
15649
|
backtest$1.strategyCoreService
|
|
14960
|
-
.getPendingSignal(symbol, context.strategyName)
|
|
15650
|
+
.getPendingSignal(true, symbol, context.strategyName)
|
|
14961
15651
|
.then(async (pendingSignal) => {
|
|
14962
15652
|
if (pendingSignal) {
|
|
14963
15653
|
return;
|
|
@@ -14997,7 +15687,7 @@ class BacktestInstance {
|
|
|
14997
15687
|
symbol,
|
|
14998
15688
|
strategyName,
|
|
14999
15689
|
});
|
|
15000
|
-
await backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
15690
|
+
await backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
|
|
15001
15691
|
};
|
|
15002
15692
|
/**
|
|
15003
15693
|
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
@@ -15025,6 +15715,7 @@ class BacktestInstance {
|
|
|
15025
15715
|
*
|
|
15026
15716
|
* @param symbol - Trading pair symbol
|
|
15027
15717
|
* @param strategyName - Strategy name to generate report for
|
|
15718
|
+
* @param columns - Optional columns configuration for the report
|
|
15028
15719
|
* @returns Promise resolving to markdown formatted report string
|
|
15029
15720
|
*
|
|
15030
15721
|
* @example
|
|
@@ -15034,12 +15725,12 @@ class BacktestInstance {
|
|
|
15034
15725
|
* console.log(markdown);
|
|
15035
15726
|
* ```
|
|
15036
15727
|
*/
|
|
15037
|
-
this.getReport = async (symbol, strategyName) => {
|
|
15728
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15038
15729
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_REPORT, {
|
|
15039
15730
|
symbol,
|
|
15040
15731
|
strategyName,
|
|
15041
15732
|
});
|
|
15042
|
-
return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName);
|
|
15733
|
+
return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName, columns);
|
|
15043
15734
|
};
|
|
15044
15735
|
/**
|
|
15045
15736
|
* Saves strategy report to disk.
|
|
@@ -15047,6 +15738,7 @@ class BacktestInstance {
|
|
|
15047
15738
|
* @param symbol - Trading pair symbol
|
|
15048
15739
|
* @param strategyName - Strategy name to save report for
|
|
15049
15740
|
* @param path - Optional directory path to save report (default: "./dump/backtest")
|
|
15741
|
+
* @param columns - Optional columns configuration for the report
|
|
15050
15742
|
*
|
|
15051
15743
|
* @example
|
|
15052
15744
|
* ```typescript
|
|
@@ -15058,13 +15750,13 @@ class BacktestInstance {
|
|
|
15058
15750
|
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15059
15751
|
* ```
|
|
15060
15752
|
*/
|
|
15061
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
15753
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15062
15754
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_DUMP, {
|
|
15063
15755
|
symbol,
|
|
15064
15756
|
strategyName,
|
|
15065
15757
|
path,
|
|
15066
15758
|
});
|
|
15067
|
-
await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path);
|
|
15759
|
+
await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15068
15760
|
};
|
|
15069
15761
|
}
|
|
15070
15762
|
}
|
|
@@ -15203,6 +15895,7 @@ class BacktestUtils {
|
|
|
15203
15895
|
*
|
|
15204
15896
|
* @param symbol - Trading pair symbol
|
|
15205
15897
|
* @param strategyName - Strategy name to generate report for
|
|
15898
|
+
* @param columns - Optional columns configuration for the report
|
|
15206
15899
|
* @returns Promise resolving to markdown formatted report string
|
|
15207
15900
|
*
|
|
15208
15901
|
* @example
|
|
@@ -15211,7 +15904,7 @@ class BacktestUtils {
|
|
|
15211
15904
|
* console.log(markdown);
|
|
15212
15905
|
* ```
|
|
15213
15906
|
*/
|
|
15214
|
-
this.getReport = async (symbol, strategyName) => {
|
|
15907
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15215
15908
|
backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
|
|
15216
15909
|
{
|
|
15217
15910
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15219,7 +15912,7 @@ class BacktestUtils {
|
|
|
15219
15912
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_REPORT));
|
|
15220
15913
|
}
|
|
15221
15914
|
const instance = this._getInstance(symbol, strategyName);
|
|
15222
|
-
return await instance.getReport(symbol, strategyName);
|
|
15915
|
+
return await instance.getReport(symbol, strategyName, columns);
|
|
15223
15916
|
};
|
|
15224
15917
|
/**
|
|
15225
15918
|
* Saves strategy report to disk.
|
|
@@ -15227,6 +15920,7 @@ class BacktestUtils {
|
|
|
15227
15920
|
* @param symbol - Trading pair symbol
|
|
15228
15921
|
* @param strategyName - Strategy name to save report for
|
|
15229
15922
|
* @param path - Optional directory path to save report (default: "./dump/backtest")
|
|
15923
|
+
* @param columns - Optional columns configuration for the report
|
|
15230
15924
|
*
|
|
15231
15925
|
* @example
|
|
15232
15926
|
* ```typescript
|
|
@@ -15237,7 +15931,7 @@ class BacktestUtils {
|
|
|
15237
15931
|
* await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15238
15932
|
* ```
|
|
15239
15933
|
*/
|
|
15240
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
15934
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15241
15935
|
backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_DUMP);
|
|
15242
15936
|
{
|
|
15243
15937
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15245,7 +15939,7 @@ class BacktestUtils {
|
|
|
15245
15939
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_DUMP));
|
|
15246
15940
|
}
|
|
15247
15941
|
const instance = this._getInstance(symbol, strategyName);
|
|
15248
|
-
return await instance.dump(symbol, strategyName, path);
|
|
15942
|
+
return await instance.dump(symbol, strategyName, path, columns);
|
|
15249
15943
|
};
|
|
15250
15944
|
/**
|
|
15251
15945
|
* Lists all active backtest instances with their current status.
|
|
@@ -15419,12 +16113,12 @@ class LiveInstance {
|
|
|
15419
16113
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName: context.strategyName });
|
|
15420
16114
|
}
|
|
15421
16115
|
{
|
|
15422
|
-
backtest$1.strategyCoreService.clear({ symbol, strategyName: context.strategyName });
|
|
16116
|
+
backtest$1.strategyCoreService.clear(false, { symbol, strategyName: context.strategyName });
|
|
15423
16117
|
}
|
|
15424
16118
|
{
|
|
15425
16119
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(context.strategyName);
|
|
15426
|
-
riskName && backtest$1.riskGlobalService.clear(riskName);
|
|
15427
|
-
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(riskName));
|
|
16120
|
+
riskName && backtest$1.riskGlobalService.clear(false, riskName);
|
|
16121
|
+
riskList && riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(false, riskName));
|
|
15428
16122
|
}
|
|
15429
16123
|
return backtest$1.liveCommandService.run(symbol, context);
|
|
15430
16124
|
};
|
|
@@ -15455,9 +16149,9 @@ class LiveInstance {
|
|
|
15455
16149
|
});
|
|
15456
16150
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
15457
16151
|
return () => {
|
|
15458
|
-
backtest$1.strategyCoreService.stop({ symbol, strategyName: context.strategyName }
|
|
16152
|
+
backtest$1.strategyCoreService.stop(false, { symbol, strategyName: context.strategyName });
|
|
15459
16153
|
backtest$1.strategyCoreService
|
|
15460
|
-
.getPendingSignal(symbol, context.strategyName)
|
|
16154
|
+
.getPendingSignal(false, symbol, context.strategyName)
|
|
15461
16155
|
.then(async (pendingSignal) => {
|
|
15462
16156
|
if (pendingSignal) {
|
|
15463
16157
|
return;
|
|
@@ -15497,7 +16191,7 @@ class LiveInstance {
|
|
|
15497
16191
|
symbol,
|
|
15498
16192
|
strategyName,
|
|
15499
16193
|
});
|
|
15500
|
-
await backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
16194
|
+
await backtest$1.strategyCoreService.stop(false, { symbol, strategyName });
|
|
15501
16195
|
};
|
|
15502
16196
|
/**
|
|
15503
16197
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
@@ -15525,6 +16219,7 @@ class LiveInstance {
|
|
|
15525
16219
|
*
|
|
15526
16220
|
* @param symbol - Trading pair symbol
|
|
15527
16221
|
* @param strategyName - Strategy name to generate report for
|
|
16222
|
+
* @param columns - Optional columns configuration for the report
|
|
15528
16223
|
* @returns Promise resolving to markdown formatted report string
|
|
15529
16224
|
*
|
|
15530
16225
|
* @example
|
|
@@ -15534,12 +16229,12 @@ class LiveInstance {
|
|
|
15534
16229
|
* console.log(markdown);
|
|
15535
16230
|
* ```
|
|
15536
16231
|
*/
|
|
15537
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16232
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15538
16233
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
|
|
15539
16234
|
symbol,
|
|
15540
16235
|
strategyName,
|
|
15541
16236
|
});
|
|
15542
|
-
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
|
|
16237
|
+
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName, columns);
|
|
15543
16238
|
};
|
|
15544
16239
|
/**
|
|
15545
16240
|
* Saves strategy report to disk.
|
|
@@ -15547,6 +16242,7 @@ class LiveInstance {
|
|
|
15547
16242
|
* @param symbol - Trading pair symbol
|
|
15548
16243
|
* @param strategyName - Strategy name to save report for
|
|
15549
16244
|
* @param path - Optional directory path to save report (default: "./dump/live")
|
|
16245
|
+
* @param columns - Optional columns configuration for the report
|
|
15550
16246
|
*
|
|
15551
16247
|
* @example
|
|
15552
16248
|
* ```typescript
|
|
@@ -15558,13 +16254,13 @@ class LiveInstance {
|
|
|
15558
16254
|
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15559
16255
|
* ```
|
|
15560
16256
|
*/
|
|
15561
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16257
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15562
16258
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
|
|
15563
16259
|
symbol,
|
|
15564
16260
|
strategyName,
|
|
15565
16261
|
path,
|
|
15566
16262
|
});
|
|
15567
|
-
await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
|
|
16263
|
+
await backtest$1.liveMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15568
16264
|
};
|
|
15569
16265
|
}
|
|
15570
16266
|
}
|
|
@@ -15714,6 +16410,7 @@ class LiveUtils {
|
|
|
15714
16410
|
*
|
|
15715
16411
|
* @param symbol - Trading pair symbol
|
|
15716
16412
|
* @param strategyName - Strategy name to generate report for
|
|
16413
|
+
* @param columns - Optional columns configuration for the report
|
|
15717
16414
|
* @returns Promise resolving to markdown formatted report string
|
|
15718
16415
|
*
|
|
15719
16416
|
* @example
|
|
@@ -15722,7 +16419,7 @@ class LiveUtils {
|
|
|
15722
16419
|
* console.log(markdown);
|
|
15723
16420
|
* ```
|
|
15724
16421
|
*/
|
|
15725
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16422
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15726
16423
|
backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_GET_REPORT);
|
|
15727
16424
|
{
|
|
15728
16425
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15730,7 +16427,7 @@ class LiveUtils {
|
|
|
15730
16427
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
|
|
15731
16428
|
}
|
|
15732
16429
|
const instance = this._getInstance(symbol, strategyName);
|
|
15733
|
-
return await instance.getReport(symbol, strategyName);
|
|
16430
|
+
return await instance.getReport(symbol, strategyName, columns);
|
|
15734
16431
|
};
|
|
15735
16432
|
/**
|
|
15736
16433
|
* Saves strategy report to disk.
|
|
@@ -15738,6 +16435,7 @@ class LiveUtils {
|
|
|
15738
16435
|
* @param symbol - Trading pair symbol
|
|
15739
16436
|
* @param strategyName - Strategy name to save report for
|
|
15740
16437
|
* @param path - Optional directory path to save report (default: "./dump/live")
|
|
16438
|
+
* @param columns - Optional columns configuration for the report
|
|
15741
16439
|
*
|
|
15742
16440
|
* @example
|
|
15743
16441
|
* ```typescript
|
|
@@ -15748,7 +16446,7 @@ class LiveUtils {
|
|
|
15748
16446
|
* await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15749
16447
|
* ```
|
|
15750
16448
|
*/
|
|
15751
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16449
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15752
16450
|
backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_DUMP);
|
|
15753
16451
|
{
|
|
15754
16452
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15756,7 +16454,7 @@ class LiveUtils {
|
|
|
15756
16454
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
|
|
15757
16455
|
}
|
|
15758
16456
|
const instance = this._getInstance(symbol, strategyName);
|
|
15759
|
-
return await instance.dump(symbol, strategyName, path);
|
|
16457
|
+
return await instance.dump(symbol, strategyName, path, columns);
|
|
15760
16458
|
};
|
|
15761
16459
|
/**
|
|
15762
16460
|
* Lists all active live trading instances with their current status.
|
|
@@ -15855,6 +16553,7 @@ class ScheduleUtils {
|
|
|
15855
16553
|
*
|
|
15856
16554
|
* @param symbol - Trading pair symbol
|
|
15857
16555
|
* @param strategyName - Strategy name to generate report for
|
|
16556
|
+
* @param columns - Optional columns configuration for the report
|
|
15858
16557
|
* @returns Promise resolving to markdown formatted report string
|
|
15859
16558
|
*
|
|
15860
16559
|
* @example
|
|
@@ -15863,7 +16562,7 @@ class ScheduleUtils {
|
|
|
15863
16562
|
* console.log(markdown);
|
|
15864
16563
|
* ```
|
|
15865
16564
|
*/
|
|
15866
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16565
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15867
16566
|
backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_REPORT, {
|
|
15868
16567
|
symbol,
|
|
15869
16568
|
strategyName,
|
|
@@ -15874,7 +16573,7 @@ class ScheduleUtils {
|
|
|
15874
16573
|
riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
|
|
15875
16574
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT));
|
|
15876
16575
|
}
|
|
15877
|
-
return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName);
|
|
16576
|
+
return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName, columns);
|
|
15878
16577
|
};
|
|
15879
16578
|
/**
|
|
15880
16579
|
* Saves strategy report to disk.
|
|
@@ -15882,6 +16581,7 @@ class ScheduleUtils {
|
|
|
15882
16581
|
* @param symbol - Trading pair symbol
|
|
15883
16582
|
* @param strategyName - Strategy name to save report for
|
|
15884
16583
|
* @param path - Optional directory path to save report (default: "./dump/schedule")
|
|
16584
|
+
* @param columns - Optional columns configuration for the report
|
|
15885
16585
|
*
|
|
15886
16586
|
* @example
|
|
15887
16587
|
* ```typescript
|
|
@@ -15892,7 +16592,7 @@ class ScheduleUtils {
|
|
|
15892
16592
|
* await Schedule.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15893
16593
|
* ```
|
|
15894
16594
|
*/
|
|
15895
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16595
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15896
16596
|
backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_DUMP, {
|
|
15897
16597
|
symbol,
|
|
15898
16598
|
strategyName,
|
|
@@ -15904,7 +16604,7 @@ class ScheduleUtils {
|
|
|
15904
16604
|
riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
|
|
15905
16605
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP));
|
|
15906
16606
|
}
|
|
15907
|
-
await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path);
|
|
16607
|
+
await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15908
16608
|
};
|
|
15909
16609
|
}
|
|
15910
16610
|
}
|
|
@@ -16004,6 +16704,7 @@ class Performance {
|
|
|
16004
16704
|
*
|
|
16005
16705
|
* @param symbol - Trading pair symbol
|
|
16006
16706
|
* @param strategyName - Strategy name to generate report for
|
|
16707
|
+
* @param columns - Optional columns configuration for the report
|
|
16007
16708
|
* @returns Markdown formatted report string
|
|
16008
16709
|
*
|
|
16009
16710
|
* @example
|
|
@@ -16016,14 +16717,14 @@ class Performance {
|
|
|
16016
16717
|
* await fs.writeFile("performance-report.md", markdown);
|
|
16017
16718
|
* ```
|
|
16018
16719
|
*/
|
|
16019
|
-
static async getReport(symbol, strategyName) {
|
|
16720
|
+
static async getReport(symbol, strategyName, columns) {
|
|
16020
16721
|
backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
16021
16722
|
{
|
|
16022
16723
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16023
16724
|
riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
16024
16725
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT));
|
|
16025
16726
|
}
|
|
16026
|
-
return backtest$1.performanceMarkdownService.getReport(symbol, strategyName);
|
|
16727
|
+
return backtest$1.performanceMarkdownService.getReport(symbol, strategyName, columns);
|
|
16027
16728
|
}
|
|
16028
16729
|
/**
|
|
16029
16730
|
* Saves performance report to disk.
|
|
@@ -16034,6 +16735,7 @@ class Performance {
|
|
|
16034
16735
|
* @param symbol - Trading pair symbol
|
|
16035
16736
|
* @param strategyName - Strategy name to save report for
|
|
16036
16737
|
* @param path - Optional custom directory path
|
|
16738
|
+
* @param columns - Optional columns configuration for the report
|
|
16037
16739
|
*
|
|
16038
16740
|
* @example
|
|
16039
16741
|
* ```typescript
|
|
@@ -16044,14 +16746,14 @@ class Performance {
|
|
|
16044
16746
|
* await Performance.dump("BTCUSDT", "my-strategy", "./reports/perf");
|
|
16045
16747
|
* ```
|
|
16046
16748
|
*/
|
|
16047
|
-
static async dump(symbol, strategyName, path = "./dump/performance") {
|
|
16749
|
+
static async dump(symbol, strategyName, path = "./dump/performance", columns) {
|
|
16048
16750
|
backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
16049
16751
|
{
|
|
16050
16752
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16051
16753
|
riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
16052
16754
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP));
|
|
16053
16755
|
}
|
|
16054
|
-
return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path);
|
|
16756
|
+
return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path, columns);
|
|
16055
16757
|
}
|
|
16056
16758
|
}
|
|
16057
16759
|
|
|
@@ -16193,12 +16895,13 @@ class WalkerInstance {
|
|
|
16193
16895
|
backtest$1.scheduleMarkdownService.clear({ symbol, strategyName });
|
|
16194
16896
|
}
|
|
16195
16897
|
{
|
|
16196
|
-
backtest$1.strategyCoreService.clear({ symbol, strategyName });
|
|
16898
|
+
backtest$1.strategyCoreService.clear(true, { symbol, strategyName });
|
|
16197
16899
|
}
|
|
16198
16900
|
{
|
|
16199
16901
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16200
|
-
riskName && backtest$1.riskGlobalService.clear(riskName);
|
|
16201
|
-
riskList &&
|
|
16902
|
+
riskName && backtest$1.riskGlobalService.clear(true, riskName);
|
|
16903
|
+
riskList &&
|
|
16904
|
+
riskList.forEach((riskName) => backtest$1.riskGlobalService.clear(true, riskName));
|
|
16202
16905
|
}
|
|
16203
16906
|
}
|
|
16204
16907
|
return backtest$1.walkerCommandService.run(symbol, {
|
|
@@ -16234,8 +16937,12 @@ class WalkerInstance {
|
|
|
16234
16937
|
this.task(symbol, context).catch((error) => exitEmitter.next(new Error(functoolsKit.getErrorMessage(error))));
|
|
16235
16938
|
return () => {
|
|
16236
16939
|
for (const strategyName of walkerSchema.strategies) {
|
|
16237
|
-
backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
16238
|
-
walkerStopSubject.next({
|
|
16940
|
+
backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
|
|
16941
|
+
walkerStopSubject.next({
|
|
16942
|
+
symbol,
|
|
16943
|
+
strategyName,
|
|
16944
|
+
walkerName: context.walkerName,
|
|
16945
|
+
});
|
|
16239
16946
|
}
|
|
16240
16947
|
if (!this._isDone) {
|
|
16241
16948
|
doneWalkerSubject.next({
|
|
@@ -16280,7 +16987,7 @@ class WalkerInstance {
|
|
|
16280
16987
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16281
16988
|
for (const strategyName of walkerSchema.strategies) {
|
|
16282
16989
|
await walkerStopSubject.next({ symbol, strategyName, walkerName });
|
|
16283
|
-
await backtest$1.strategyCoreService.stop({ symbol, strategyName }
|
|
16990
|
+
await backtest$1.strategyCoreService.stop(true, { symbol, strategyName });
|
|
16284
16991
|
}
|
|
16285
16992
|
};
|
|
16286
16993
|
/**
|
|
@@ -16313,6 +17020,8 @@ class WalkerInstance {
|
|
|
16313
17020
|
*
|
|
16314
17021
|
* @param symbol - Trading symbol
|
|
16315
17022
|
* @param walkerName - Walker name to generate report for
|
|
17023
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17024
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16316
17025
|
* @returns Promise resolving to markdown formatted report string
|
|
16317
17026
|
*
|
|
16318
17027
|
* @example
|
|
@@ -16322,7 +17031,7 @@ class WalkerInstance {
|
|
|
16322
17031
|
* console.log(markdown);
|
|
16323
17032
|
* ```
|
|
16324
17033
|
*/
|
|
16325
|
-
this.getReport = async (symbol, walkerName) => {
|
|
17034
|
+
this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
|
|
16326
17035
|
backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_REPORT, {
|
|
16327
17036
|
symbol,
|
|
16328
17037
|
walkerName,
|
|
@@ -16331,7 +17040,7 @@ class WalkerInstance {
|
|
|
16331
17040
|
return await backtest$1.walkerMarkdownService.getReport(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
|
|
16332
17041
|
exchangeName: walkerSchema.exchangeName,
|
|
16333
17042
|
frameName: walkerSchema.frameName,
|
|
16334
|
-
});
|
|
17043
|
+
}, strategyColumns, pnlColumns);
|
|
16335
17044
|
};
|
|
16336
17045
|
/**
|
|
16337
17046
|
* Saves walker report to disk.
|
|
@@ -16339,6 +17048,8 @@ class WalkerInstance {
|
|
|
16339
17048
|
* @param symbol - Trading symbol
|
|
16340
17049
|
* @param walkerName - Walker name to save report for
|
|
16341
17050
|
* @param path - Optional directory path to save report (default: "./dump/walker")
|
|
17051
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17052
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16342
17053
|
*
|
|
16343
17054
|
* @example
|
|
16344
17055
|
* ```typescript
|
|
@@ -16350,7 +17061,7 @@ class WalkerInstance {
|
|
|
16350
17061
|
* await instance.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
16351
17062
|
* ```
|
|
16352
17063
|
*/
|
|
16353
|
-
this.dump = async (symbol, walkerName, path) => {
|
|
17064
|
+
this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
|
|
16354
17065
|
backtest$1.loggerService.info(WALKER_METHOD_NAME_DUMP, {
|
|
16355
17066
|
symbol,
|
|
16356
17067
|
walkerName,
|
|
@@ -16360,7 +17071,7 @@ class WalkerInstance {
|
|
|
16360
17071
|
await backtest$1.walkerMarkdownService.dump(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
|
|
16361
17072
|
exchangeName: walkerSchema.exchangeName,
|
|
16362
17073
|
frameName: walkerSchema.frameName,
|
|
16363
|
-
}, path);
|
|
17074
|
+
}, path, strategyColumns, pnlColumns);
|
|
16364
17075
|
};
|
|
16365
17076
|
}
|
|
16366
17077
|
}
|
|
@@ -16405,8 +17116,10 @@ class WalkerUtils {
|
|
|
16405
17116
|
for (const strategyName of walkerSchema.strategies) {
|
|
16406
17117
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_RUN);
|
|
16407
17118
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16408
|
-
riskName &&
|
|
16409
|
-
|
|
17119
|
+
riskName &&
|
|
17120
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN);
|
|
17121
|
+
riskList &&
|
|
17122
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_RUN));
|
|
16410
17123
|
}
|
|
16411
17124
|
const instance = this._getInstance(symbol, context.walkerName);
|
|
16412
17125
|
return instance.run(symbol, context);
|
|
@@ -16438,8 +17151,10 @@ class WalkerUtils {
|
|
|
16438
17151
|
for (const strategyName of walkerSchema.strategies) {
|
|
16439
17152
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_BACKGROUND);
|
|
16440
17153
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16441
|
-
riskName &&
|
|
16442
|
-
|
|
17154
|
+
riskName &&
|
|
17155
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND);
|
|
17156
|
+
riskList &&
|
|
17157
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_BACKGROUND));
|
|
16443
17158
|
}
|
|
16444
17159
|
const instance = this._getInstance(symbol, context.walkerName);
|
|
16445
17160
|
return instance.background(symbol, context);
|
|
@@ -16473,8 +17188,10 @@ class WalkerUtils {
|
|
|
16473
17188
|
for (const strategyName of walkerSchema.strategies) {
|
|
16474
17189
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_STOP);
|
|
16475
17190
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16476
|
-
riskName &&
|
|
16477
|
-
|
|
17191
|
+
riskName &&
|
|
17192
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP);
|
|
17193
|
+
riskList &&
|
|
17194
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_STOP));
|
|
16478
17195
|
}
|
|
16479
17196
|
const instance = this._getInstance(symbol, walkerName);
|
|
16480
17197
|
return await instance.stop(symbol, walkerName);
|
|
@@ -16498,8 +17215,10 @@ class WalkerUtils {
|
|
|
16498
17215
|
for (const strategyName of walkerSchema.strategies) {
|
|
16499
17216
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_GET_DATA);
|
|
16500
17217
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16501
|
-
riskName &&
|
|
16502
|
-
|
|
17218
|
+
riskName &&
|
|
17219
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA);
|
|
17220
|
+
riskList &&
|
|
17221
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_DATA));
|
|
16503
17222
|
}
|
|
16504
17223
|
const instance = this._getInstance(symbol, walkerName);
|
|
16505
17224
|
return await instance.getData(symbol, walkerName);
|
|
@@ -16509,6 +17228,8 @@ class WalkerUtils {
|
|
|
16509
17228
|
*
|
|
16510
17229
|
* @param symbol - Trading symbol
|
|
16511
17230
|
* @param walkerName - Walker name to generate report for
|
|
17231
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17232
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16512
17233
|
* @returns Promise resolving to markdown formatted report string
|
|
16513
17234
|
*
|
|
16514
17235
|
* @example
|
|
@@ -16517,17 +17238,19 @@ class WalkerUtils {
|
|
|
16517
17238
|
* console.log(markdown);
|
|
16518
17239
|
* ```
|
|
16519
17240
|
*/
|
|
16520
|
-
this.getReport = async (symbol, walkerName) => {
|
|
17241
|
+
this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
|
|
16521
17242
|
backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_GET_REPORT);
|
|
16522
17243
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16523
17244
|
for (const strategyName of walkerSchema.strategies) {
|
|
16524
17245
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_GET_REPORT);
|
|
16525
17246
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16526
|
-
riskName &&
|
|
16527
|
-
|
|
17247
|
+
riskName &&
|
|
17248
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT);
|
|
17249
|
+
riskList &&
|
|
17250
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT));
|
|
16528
17251
|
}
|
|
16529
17252
|
const instance = this._getInstance(symbol, walkerName);
|
|
16530
|
-
return await instance.getReport(symbol, walkerName);
|
|
17253
|
+
return await instance.getReport(symbol, walkerName, strategyColumns, pnlColumns);
|
|
16531
17254
|
};
|
|
16532
17255
|
/**
|
|
16533
17256
|
* Saves walker report to disk.
|
|
@@ -16535,6 +17258,8 @@ class WalkerUtils {
|
|
|
16535
17258
|
* @param symbol - Trading symbol
|
|
16536
17259
|
* @param walkerName - Walker name to save report for
|
|
16537
17260
|
* @param path - Optional directory path to save report (default: "./dump/walker")
|
|
17261
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17262
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16538
17263
|
*
|
|
16539
17264
|
* @example
|
|
16540
17265
|
* ```typescript
|
|
@@ -16545,17 +17270,19 @@ class WalkerUtils {
|
|
|
16545
17270
|
* await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
16546
17271
|
* ```
|
|
16547
17272
|
*/
|
|
16548
|
-
this.dump = async (symbol, walkerName, path) => {
|
|
17273
|
+
this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
|
|
16549
17274
|
backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_DUMP);
|
|
16550
17275
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16551
17276
|
for (const strategyName of walkerSchema.strategies) {
|
|
16552
17277
|
backtest$1.strategyValidationService.validate(strategyName, WALKER_METHOD_NAME_DUMP);
|
|
16553
17278
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16554
|
-
riskName &&
|
|
16555
|
-
|
|
17279
|
+
riskName &&
|
|
17280
|
+
backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP);
|
|
17281
|
+
riskList &&
|
|
17282
|
+
riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP));
|
|
16556
17283
|
}
|
|
16557
17284
|
const instance = this._getInstance(symbol, walkerName);
|
|
16558
|
-
return await instance.dump(symbol, walkerName, path);
|
|
17285
|
+
return await instance.dump(symbol, walkerName, path, strategyColumns, pnlColumns);
|
|
16559
17286
|
};
|
|
16560
17287
|
/**
|
|
16561
17288
|
* Lists all active walker instances with their current status.
|
|
@@ -16661,6 +17388,7 @@ class HeatUtils {
|
|
|
16661
17388
|
* Symbols are sorted by Total PNL descending.
|
|
16662
17389
|
*
|
|
16663
17390
|
* @param strategyName - Strategy name to generate heatmap report for
|
|
17391
|
+
* @param columns - Optional columns configuration for the report
|
|
16664
17392
|
* @returns Promise resolving to markdown formatted report string
|
|
16665
17393
|
*
|
|
16666
17394
|
* @example
|
|
@@ -16679,7 +17407,7 @@ class HeatUtils {
|
|
|
16679
17407
|
* // ...
|
|
16680
17408
|
* ```
|
|
16681
17409
|
*/
|
|
16682
|
-
this.getReport = async (strategyName) => {
|
|
17410
|
+
this.getReport = async (strategyName, columns) => {
|
|
16683
17411
|
backtest$1.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName });
|
|
16684
17412
|
backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_GET_REPORT);
|
|
16685
17413
|
{
|
|
@@ -16687,7 +17415,7 @@ class HeatUtils {
|
|
|
16687
17415
|
riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
|
|
16688
17416
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT));
|
|
16689
17417
|
}
|
|
16690
|
-
return await backtest$1.heatMarkdownService.getReport(strategyName);
|
|
17418
|
+
return await backtest$1.heatMarkdownService.getReport(strategyName, columns);
|
|
16691
17419
|
};
|
|
16692
17420
|
/**
|
|
16693
17421
|
* Saves heatmap report to disk for a strategy.
|
|
@@ -16697,6 +17425,7 @@ class HeatUtils {
|
|
|
16697
17425
|
*
|
|
16698
17426
|
* @param strategyName - Strategy name to save heatmap report for
|
|
16699
17427
|
* @param path - Optional directory path to save report (default: "./dump/heatmap")
|
|
17428
|
+
* @param columns - Optional columns configuration for the report
|
|
16700
17429
|
*
|
|
16701
17430
|
* @example
|
|
16702
17431
|
* ```typescript
|
|
@@ -16707,7 +17436,7 @@ class HeatUtils {
|
|
|
16707
17436
|
* await Heat.dump("my-strategy", "./reports");
|
|
16708
17437
|
* ```
|
|
16709
17438
|
*/
|
|
16710
|
-
this.dump = async (strategyName, path) => {
|
|
17439
|
+
this.dump = async (strategyName, path, columns) => {
|
|
16711
17440
|
backtest$1.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName, path });
|
|
16712
17441
|
backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_DUMP);
|
|
16713
17442
|
{
|
|
@@ -16715,7 +17444,7 @@ class HeatUtils {
|
|
|
16715
17444
|
riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
|
|
16716
17445
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP));
|
|
16717
17446
|
}
|
|
16718
|
-
await backtest$1.heatMarkdownService.dump(strategyName, path);
|
|
17447
|
+
await backtest$1.heatMarkdownService.dump(strategyName, path, columns);
|
|
16719
17448
|
};
|
|
16720
17449
|
}
|
|
16721
17450
|
}
|
|
@@ -17015,7 +17744,7 @@ class PartialUtils {
|
|
|
17015
17744
|
*
|
|
17016
17745
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17017
17746
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17018
|
-
* @returns Promise resolving to
|
|
17747
|
+
* @returns Promise resolving to PartialStatisticsModel object with counts and event list
|
|
17019
17748
|
*
|
|
17020
17749
|
* @example
|
|
17021
17750
|
* ```typescript
|
|
@@ -17059,6 +17788,7 @@ class PartialUtils {
|
|
|
17059
17788
|
*
|
|
17060
17789
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17061
17790
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17791
|
+
* @param columns - Optional columns configuration for the report
|
|
17062
17792
|
* @returns Promise resolving to markdown formatted report string
|
|
17063
17793
|
*
|
|
17064
17794
|
* @example
|
|
@@ -17079,7 +17809,7 @@ class PartialUtils {
|
|
|
17079
17809
|
* // **Loss events:** 1
|
|
17080
17810
|
* ```
|
|
17081
17811
|
*/
|
|
17082
|
-
this.getReport = async (symbol, strategyName) => {
|
|
17812
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
17083
17813
|
backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName });
|
|
17084
17814
|
backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
17085
17815
|
{
|
|
@@ -17087,7 +17817,7 @@ class PartialUtils {
|
|
|
17087
17817
|
riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
17088
17818
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT));
|
|
17089
17819
|
}
|
|
17090
|
-
return await backtest$1.partialMarkdownService.getReport(symbol, strategyName);
|
|
17820
|
+
return await backtest$1.partialMarkdownService.getReport(symbol, strategyName, columns);
|
|
17091
17821
|
};
|
|
17092
17822
|
/**
|
|
17093
17823
|
* Generates and saves markdown report to file.
|
|
@@ -17104,6 +17834,7 @@ class PartialUtils {
|
|
|
17104
17834
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17105
17835
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17106
17836
|
* @param path - Output directory path (default: "./dump/partial")
|
|
17837
|
+
* @param columns - Optional columns configuration for the report
|
|
17107
17838
|
* @returns Promise that resolves when file is written
|
|
17108
17839
|
*
|
|
17109
17840
|
* @example
|
|
@@ -17120,7 +17851,7 @@ class PartialUtils {
|
|
|
17120
17851
|
* }
|
|
17121
17852
|
* ```
|
|
17122
17853
|
*/
|
|
17123
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
17854
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
17124
17855
|
backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName, path });
|
|
17125
17856
|
backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_DUMP);
|
|
17126
17857
|
{
|
|
@@ -17128,7 +17859,7 @@ class PartialUtils {
|
|
|
17128
17859
|
riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
|
|
17129
17860
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP));
|
|
17130
17861
|
}
|
|
17131
|
-
await backtest$1.partialMarkdownService.dump(symbol, strategyName, path);
|
|
17862
|
+
await backtest$1.partialMarkdownService.dump(symbol, strategyName, path, columns);
|
|
17132
17863
|
};
|
|
17133
17864
|
}
|
|
17134
17865
|
}
|
|
@@ -17260,8 +17991,10 @@ exports.formatPrice = formatPrice;
|
|
|
17260
17991
|
exports.formatQuantity = formatQuantity;
|
|
17261
17992
|
exports.getAveragePrice = getAveragePrice;
|
|
17262
17993
|
exports.getCandles = getCandles;
|
|
17994
|
+
exports.getColumns = getColumns;
|
|
17263
17995
|
exports.getConfig = getConfig;
|
|
17264
17996
|
exports.getDate = getDate;
|
|
17997
|
+
exports.getDefaultColumns = getDefaultColumns;
|
|
17265
17998
|
exports.getDefaultConfig = getDefaultConfig;
|
|
17266
17999
|
exports.getMode = getMode;
|
|
17267
18000
|
exports.lib = backtest;
|
|
@@ -17300,5 +18033,6 @@ exports.listenWalker = listenWalker;
|
|
|
17300
18033
|
exports.listenWalkerComplete = listenWalkerComplete;
|
|
17301
18034
|
exports.listenWalkerOnce = listenWalkerOnce;
|
|
17302
18035
|
exports.listenWalkerProgress = listenWalkerProgress;
|
|
18036
|
+
exports.setColumns = setColumns;
|
|
17303
18037
|
exports.setConfig = setConfig;
|
|
17304
18038
|
exports.setLogger = setLogger;
|