backtest-kit 1.5.25 → 1.5.26
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 +1618 -959
- package/build/index.mjs +1617 -961
- package/package.json +1 -1
- package/types.d.ts +4914 -4338
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'),
|
|
@@ -1765,53 +2841,6 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
1765
2841
|
walkerStopSubject: walkerStopSubject
|
|
1766
2842
|
});
|
|
1767
2843
|
|
|
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
2844
|
const INTERVAL_MINUTES$1 = {
|
|
1816
2845
|
"1m": 1,
|
|
1817
2846
|
"3m": 3,
|
|
@@ -3390,7 +4419,7 @@ class RiskUtils {
|
|
|
3390
4419
|
*
|
|
3391
4420
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3392
4421
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
3393
|
-
* @returns Promise resolving to
|
|
4422
|
+
* @returns Promise resolving to RiskStatisticsModel object with counts and event list
|
|
3394
4423
|
*
|
|
3395
4424
|
* @example
|
|
3396
4425
|
* ```typescript
|
|
@@ -3437,6 +4466,7 @@ class RiskUtils {
|
|
|
3437
4466
|
*
|
|
3438
4467
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3439
4468
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
4469
|
+
* @param columns - Optional columns configuration for the report
|
|
3440
4470
|
* @returns Promise resolving to markdown formatted report string
|
|
3441
4471
|
*
|
|
3442
4472
|
* @example
|
|
@@ -3460,7 +4490,7 @@ class RiskUtils {
|
|
|
3460
4490
|
* // - my-strategy: 1
|
|
3461
4491
|
* ```
|
|
3462
4492
|
*/
|
|
3463
|
-
this.getReport = async (symbol, strategyName) => {
|
|
4493
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
3464
4494
|
backtest$1.loggerService.info(RISK_METHOD_NAME_GET_REPORT, {
|
|
3465
4495
|
symbol,
|
|
3466
4496
|
strategyName,
|
|
@@ -3472,7 +4502,7 @@ class RiskUtils {
|
|
|
3472
4502
|
backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT);
|
|
3473
4503
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_GET_REPORT));
|
|
3474
4504
|
}
|
|
3475
|
-
return await backtest$1.riskMarkdownService.getReport(symbol, strategyName);
|
|
4505
|
+
return await backtest$1.riskMarkdownService.getReport(symbol, strategyName, columns);
|
|
3476
4506
|
};
|
|
3477
4507
|
/**
|
|
3478
4508
|
* Generates and saves markdown report to file.
|
|
@@ -3489,6 +4519,7 @@ class RiskUtils {
|
|
|
3489
4519
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
3490
4520
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
3491
4521
|
* @param path - Output directory path (default: "./dump/risk")
|
|
4522
|
+
* @param columns - Optional columns configuration for the report
|
|
3492
4523
|
* @returns Promise that resolves when file is written
|
|
3493
4524
|
*
|
|
3494
4525
|
* @example
|
|
@@ -3505,7 +4536,7 @@ class RiskUtils {
|
|
|
3505
4536
|
* }
|
|
3506
4537
|
* ```
|
|
3507
4538
|
*/
|
|
3508
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
4539
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
3509
4540
|
backtest$1.loggerService.info(RISK_METHOD_NAME_DUMP, {
|
|
3510
4541
|
symbol,
|
|
3511
4542
|
strategyName,
|
|
@@ -3518,7 +4549,7 @@ class RiskUtils {
|
|
|
3518
4549
|
backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP);
|
|
3519
4550
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, RISK_METHOD_NAME_DUMP));
|
|
3520
4551
|
}
|
|
3521
|
-
await backtest$1.riskMarkdownService.dump(symbol, strategyName, path);
|
|
4552
|
+
await backtest$1.riskMarkdownService.dump(symbol, strategyName, path, columns);
|
|
3522
4553
|
};
|
|
3523
4554
|
}
|
|
3524
4555
|
}
|
|
@@ -5035,6 +6066,19 @@ class StrategySchemaService {
|
|
|
5035
6066
|
if (typeof strategySchema.strategyName !== "string") {
|
|
5036
6067
|
throw new Error(`strategy schema validation failed: missing strategyName`);
|
|
5037
6068
|
}
|
|
6069
|
+
if (strategySchema.riskName && typeof strategySchema.riskName !== "string") {
|
|
6070
|
+
throw new Error(`strategy schema validation failed: invalid riskName`);
|
|
6071
|
+
}
|
|
6072
|
+
if (strategySchema.riskList && !Array.isArray(strategySchema.riskList)) {
|
|
6073
|
+
throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} system=${strategySchema.riskList}`);
|
|
6074
|
+
}
|
|
6075
|
+
if (strategySchema.riskList &&
|
|
6076
|
+
strategySchema.riskList.length !== new Set(strategySchema.riskList).size) {
|
|
6077
|
+
throw new Error(`strategy schema validation failed: found duplicate riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
|
|
6078
|
+
}
|
|
6079
|
+
if (strategySchema.riskList?.some((value) => typeof value !== "string")) {
|
|
6080
|
+
throw new Error(`strategy schema validation failed: invalid riskList for strategyName=${strategySchema.strategyName} riskList=[${strategySchema.riskList}]`);
|
|
6081
|
+
}
|
|
5038
6082
|
if (typeof strategySchema.interval !== "string") {
|
|
5039
6083
|
throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
|
|
5040
6084
|
}
|
|
@@ -6265,178 +7309,91 @@ class BacktestCommandService {
|
|
|
6265
7309
|
* @returns Async generator yielding closed signals with PNL
|
|
6266
7310
|
*/
|
|
6267
7311
|
this.run = (symbol, context) => {
|
|
6268
|
-
this.loggerService.log(METHOD_NAME_RUN$1, {
|
|
6269
|
-
symbol,
|
|
6270
|
-
context,
|
|
6271
|
-
});
|
|
6272
|
-
{
|
|
6273
|
-
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
|
|
6274
|
-
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
|
|
6275
|
-
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
|
|
6276
|
-
}
|
|
6277
|
-
{
|
|
6278
|
-
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
|
-
];
|
|
7312
|
+
this.loggerService.log(METHOD_NAME_RUN$1, {
|
|
7313
|
+
symbol,
|
|
7314
|
+
context,
|
|
7315
|
+
});
|
|
7316
|
+
{
|
|
7317
|
+
this.strategyValidationService.validate(context.strategyName, METHOD_NAME_RUN$1);
|
|
7318
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN$1);
|
|
7319
|
+
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN$1);
|
|
7320
|
+
}
|
|
7321
|
+
{
|
|
7322
|
+
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
7323
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1);
|
|
7324
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN$1));
|
|
7325
|
+
}
|
|
7326
|
+
return this.backtestLogicPublicService.run(symbol, context);
|
|
7327
|
+
};
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
|
|
7331
|
+
const METHOD_NAME_RUN = "walkerCommandService run";
|
|
7332
|
+
/**
|
|
7333
|
+
* Global service providing access to walker functionality.
|
|
7334
|
+
*
|
|
7335
|
+
* Simple wrapper around WalkerLogicPublicService for dependency injection.
|
|
7336
|
+
* Used by public API exports.
|
|
7337
|
+
*/
|
|
7338
|
+
class WalkerCommandService {
|
|
7339
|
+
constructor() {
|
|
7340
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
7341
|
+
this.walkerLogicPublicService = inject(TYPES.walkerLogicPublicService);
|
|
7342
|
+
this.walkerSchemaService = inject(TYPES.walkerSchemaService);
|
|
7343
|
+
this.strategyValidationService = inject(TYPES.strategyValidationService);
|
|
7344
|
+
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
7345
|
+
this.frameValidationService = inject(TYPES.frameValidationService);
|
|
7346
|
+
this.walkerValidationService = inject(TYPES.walkerValidationService);
|
|
7347
|
+
this.strategySchemaService = inject(TYPES.strategySchemaService);
|
|
7348
|
+
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
7349
|
+
/**
|
|
7350
|
+
* Runs walker comparison for a symbol with context propagation.
|
|
7351
|
+
*
|
|
7352
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
7353
|
+
* @param context - Walker context with strategies and metric
|
|
7354
|
+
*/
|
|
7355
|
+
this.run = (symbol, context) => {
|
|
7356
|
+
this.loggerService.log(METHOD_NAME_RUN, {
|
|
7357
|
+
symbol,
|
|
7358
|
+
context,
|
|
7359
|
+
});
|
|
7360
|
+
{
|
|
7361
|
+
this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_RUN);
|
|
7362
|
+
this.frameValidationService.validate(context.frameName, METHOD_NAME_RUN);
|
|
7363
|
+
this.walkerValidationService.validate(context.walkerName, METHOD_NAME_RUN);
|
|
7364
|
+
}
|
|
7365
|
+
{
|
|
7366
|
+
const walkerSchema = this.walkerSchemaService.get(context.walkerName);
|
|
7367
|
+
for (const strategyName of walkerSchema.strategies) {
|
|
7368
|
+
const { riskName, riskList } = this.strategySchemaService.get(strategyName);
|
|
7369
|
+
this.strategyValidationService.validate(strategyName, METHOD_NAME_RUN);
|
|
7370
|
+
riskName && this.riskValidationService.validate(riskName, METHOD_NAME_RUN);
|
|
7371
|
+
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_RUN));
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
return this.walkerLogicPublicService.run(symbol, context);
|
|
7375
|
+
};
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
|
|
7379
|
+
/**
|
|
7380
|
+
* Checks if a value is unsafe for display (not a number, NaN, or Infinity).
|
|
7381
|
+
*
|
|
7382
|
+
* @param value - Value to check
|
|
7383
|
+
* @returns true if value is unsafe, false otherwise
|
|
7384
|
+
*/
|
|
7385
|
+
function isUnsafe$3(value) {
|
|
7386
|
+
if (typeof value !== "number") {
|
|
7387
|
+
return true;
|
|
7388
|
+
}
|
|
7389
|
+
if (isNaN(value)) {
|
|
7390
|
+
return true;
|
|
7391
|
+
}
|
|
7392
|
+
if (!isFinite(value)) {
|
|
7393
|
+
return true;
|
|
7394
|
+
}
|
|
7395
|
+
return false;
|
|
7396
|
+
}
|
|
6440
7397
|
/** Maximum number of signals to store in backtest reports */
|
|
6441
7398
|
const MAX_EVENTS$6 = 250;
|
|
6442
7399
|
/**
|
|
@@ -6530,9 +7487,10 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6530
7487
|
* Generates markdown report with all closed signals for a strategy (View).
|
|
6531
7488
|
*
|
|
6532
7489
|
* @param strategyName - Strategy name
|
|
7490
|
+
* @param columns - Column configuration for formatting the table
|
|
6533
7491
|
* @returns Markdown formatted report with all signals
|
|
6534
7492
|
*/
|
|
6535
|
-
async getReport(strategyName) {
|
|
7493
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.backtest_columns) {
|
|
6536
7494
|
const stats = await this.getData();
|
|
6537
7495
|
if (stats.totalSignals === 0) {
|
|
6538
7496
|
return [
|
|
@@ -6541,10 +7499,15 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6541
7499
|
"No signals closed yet."
|
|
6542
7500
|
].join("\n");
|
|
6543
7501
|
}
|
|
6544
|
-
const visibleColumns =
|
|
7502
|
+
const visibleColumns = [];
|
|
7503
|
+
for (const col of columns) {
|
|
7504
|
+
if (await col.isVisible()) {
|
|
7505
|
+
visibleColumns.push(col);
|
|
7506
|
+
}
|
|
7507
|
+
}
|
|
6545
7508
|
const header = visibleColumns.map((col) => col.label);
|
|
6546
7509
|
const separator = visibleColumns.map(() => "---");
|
|
6547
|
-
const rows = this._signalList.map((closedSignal) => visibleColumns.map((col) => col.format(closedSignal)));
|
|
7510
|
+
const rows = await Promise.all(this._signalList.map(async (closedSignal, index) => Promise.all(visibleColumns.map((col) => col.format(closedSignal, index)))));
|
|
6548
7511
|
const tableData = [header, separator, ...rows];
|
|
6549
7512
|
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
6550
7513
|
return [
|
|
@@ -6569,9 +7532,10 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
6569
7532
|
*
|
|
6570
7533
|
* @param strategyName - Strategy name
|
|
6571
7534
|
* @param path - Directory path to save report (default: "./dump/backtest")
|
|
7535
|
+
* @param columns - Column configuration for formatting the table
|
|
6572
7536
|
*/
|
|
6573
|
-
async dump(strategyName, path$1 = "./dump/backtest") {
|
|
6574
|
-
const markdown = await this.getReport(strategyName);
|
|
7537
|
+
async dump(strategyName, path$1 = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
|
|
7538
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
6575
7539
|
try {
|
|
6576
7540
|
const dir = path.join(process.cwd(), path$1);
|
|
6577
7541
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -6679,6 +7643,7 @@ class BacktestMarkdownService {
|
|
|
6679
7643
|
*
|
|
6680
7644
|
* @param symbol - Trading pair symbol
|
|
6681
7645
|
* @param strategyName - Strategy name to generate report for
|
|
7646
|
+
* @param columns - Column configuration for formatting the table
|
|
6682
7647
|
* @returns Markdown formatted report string with table of all closed signals
|
|
6683
7648
|
*
|
|
6684
7649
|
* @example
|
|
@@ -6688,13 +7653,13 @@ class BacktestMarkdownService {
|
|
|
6688
7653
|
* console.log(markdown);
|
|
6689
7654
|
* ```
|
|
6690
7655
|
*/
|
|
6691
|
-
this.getReport = async (symbol, strategyName) => {
|
|
7656
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.backtest_columns) => {
|
|
6692
7657
|
this.loggerService.log("backtestMarkdownService getReport", {
|
|
6693
7658
|
symbol,
|
|
6694
7659
|
strategyName,
|
|
6695
7660
|
});
|
|
6696
7661
|
const storage = this.getStorage(symbol, strategyName);
|
|
6697
|
-
return storage.getReport(strategyName);
|
|
7662
|
+
return storage.getReport(strategyName, columns);
|
|
6698
7663
|
};
|
|
6699
7664
|
/**
|
|
6700
7665
|
* Saves symbol-strategy report to disk.
|
|
@@ -6704,6 +7669,7 @@ class BacktestMarkdownService {
|
|
|
6704
7669
|
* @param symbol - Trading pair symbol
|
|
6705
7670
|
* @param strategyName - Strategy name to save report for
|
|
6706
7671
|
* @param path - Directory path to save report (default: "./dump/backtest")
|
|
7672
|
+
* @param columns - Column configuration for formatting the table
|
|
6707
7673
|
*
|
|
6708
7674
|
* @example
|
|
6709
7675
|
* ```typescript
|
|
@@ -6716,14 +7682,14 @@ class BacktestMarkdownService {
|
|
|
6716
7682
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
6717
7683
|
* ```
|
|
6718
7684
|
*/
|
|
6719
|
-
this.dump = async (symbol, strategyName, path = "./dump/backtest") => {
|
|
7685
|
+
this.dump = async (symbol, strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) => {
|
|
6720
7686
|
this.loggerService.log("backtestMarkdownService dump", {
|
|
6721
7687
|
symbol,
|
|
6722
7688
|
strategyName,
|
|
6723
7689
|
path,
|
|
6724
7690
|
});
|
|
6725
7691
|
const storage = this.getStorage(symbol, strategyName);
|
|
6726
|
-
await storage.dump(strategyName, path);
|
|
7692
|
+
await storage.dump(strategyName, path, columns);
|
|
6727
7693
|
};
|
|
6728
7694
|
/**
|
|
6729
7695
|
* Clears accumulated signal data from storage.
|
|
@@ -6791,104 +7757,6 @@ function isUnsafe$2(value) {
|
|
|
6791
7757
|
}
|
|
6792
7758
|
return false;
|
|
6793
7759
|
}
|
|
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
7760
|
/** Maximum number of events to store in live trading reports */
|
|
6893
7761
|
const MAX_EVENTS$5 = 250;
|
|
6894
7762
|
/**
|
|
@@ -7100,9 +7968,10 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7100
7968
|
* Generates markdown report with all tick events for a strategy (View).
|
|
7101
7969
|
*
|
|
7102
7970
|
* @param strategyName - Strategy name
|
|
7971
|
+
* @param columns - Column configuration for formatting the table
|
|
7103
7972
|
* @returns Markdown formatted report with all events
|
|
7104
7973
|
*/
|
|
7105
|
-
async getReport(strategyName) {
|
|
7974
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.live_columns) {
|
|
7106
7975
|
const stats = await this.getData();
|
|
7107
7976
|
if (stats.totalEvents === 0) {
|
|
7108
7977
|
return [
|
|
@@ -7111,10 +7980,15 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7111
7980
|
"No events recorded yet."
|
|
7112
7981
|
].join("\n");
|
|
7113
7982
|
}
|
|
7114
|
-
const visibleColumns =
|
|
7983
|
+
const visibleColumns = [];
|
|
7984
|
+
for (const col of columns) {
|
|
7985
|
+
if (await col.isVisible()) {
|
|
7986
|
+
visibleColumns.push(col);
|
|
7987
|
+
}
|
|
7988
|
+
}
|
|
7115
7989
|
const header = visibleColumns.map((col) => col.label);
|
|
7116
7990
|
const separator = visibleColumns.map(() => "---");
|
|
7117
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
7991
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
7118
7992
|
const tableData = [header, separator, ...rows];
|
|
7119
7993
|
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
7120
7994
|
return [
|
|
@@ -7139,9 +8013,10 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
7139
8013
|
*
|
|
7140
8014
|
* @param strategyName - Strategy name
|
|
7141
8015
|
* @param path - Directory path to save report (default: "./dump/live")
|
|
8016
|
+
* @param columns - Column configuration for formatting the table
|
|
7142
8017
|
*/
|
|
7143
|
-
async dump(strategyName, path$1 = "./dump/live") {
|
|
7144
|
-
const markdown = await this.getReport(strategyName);
|
|
8018
|
+
async dump(strategyName, path$1 = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
|
|
8019
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
7145
8020
|
try {
|
|
7146
8021
|
const dir = path.join(process.cwd(), path$1);
|
|
7147
8022
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -7262,6 +8137,7 @@ class LiveMarkdownService {
|
|
|
7262
8137
|
*
|
|
7263
8138
|
* @param symbol - Trading pair symbol
|
|
7264
8139
|
* @param strategyName - Strategy name to generate report for
|
|
8140
|
+
* @param columns - Column configuration for formatting the table
|
|
7265
8141
|
* @returns Markdown formatted report string with table of all events
|
|
7266
8142
|
*
|
|
7267
8143
|
* @example
|
|
@@ -7271,13 +8147,13 @@ class LiveMarkdownService {
|
|
|
7271
8147
|
* console.log(markdown);
|
|
7272
8148
|
* ```
|
|
7273
8149
|
*/
|
|
7274
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8150
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.live_columns) => {
|
|
7275
8151
|
this.loggerService.log("liveMarkdownService getReport", {
|
|
7276
8152
|
symbol,
|
|
7277
8153
|
strategyName,
|
|
7278
8154
|
});
|
|
7279
8155
|
const storage = this.getStorage(symbol, strategyName);
|
|
7280
|
-
return storage.getReport(strategyName);
|
|
8156
|
+
return storage.getReport(strategyName, columns);
|
|
7281
8157
|
};
|
|
7282
8158
|
/**
|
|
7283
8159
|
* Saves symbol-strategy report to disk.
|
|
@@ -7287,6 +8163,7 @@ class LiveMarkdownService {
|
|
|
7287
8163
|
* @param symbol - Trading pair symbol
|
|
7288
8164
|
* @param strategyName - Strategy name to save report for
|
|
7289
8165
|
* @param path - Directory path to save report (default: "./dump/live")
|
|
8166
|
+
* @param columns - Column configuration for formatting the table
|
|
7290
8167
|
*
|
|
7291
8168
|
* @example
|
|
7292
8169
|
* ```typescript
|
|
@@ -7299,14 +8176,14 @@ class LiveMarkdownService {
|
|
|
7299
8176
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
7300
8177
|
* ```
|
|
7301
8178
|
*/
|
|
7302
|
-
this.dump = async (symbol, strategyName, path = "./dump/live") => {
|
|
8179
|
+
this.dump = async (symbol, strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) => {
|
|
7303
8180
|
this.loggerService.log("liveMarkdownService dump", {
|
|
7304
8181
|
symbol,
|
|
7305
8182
|
strategyName,
|
|
7306
8183
|
path,
|
|
7307
8184
|
});
|
|
7308
8185
|
const storage = this.getStorage(symbol, strategyName);
|
|
7309
|
-
await storage.dump(strategyName, path);
|
|
8186
|
+
await storage.dump(strategyName, path, columns);
|
|
7310
8187
|
};
|
|
7311
8188
|
/**
|
|
7312
8189
|
* Clears accumulated event data from storage.
|
|
@@ -7353,77 +8230,9 @@ class LiveMarkdownService {
|
|
|
7353
8230
|
this.loggerService.log("liveMarkdownService init");
|
|
7354
8231
|
signalLiveEmitter.subscribe(this.tick);
|
|
7355
8232
|
});
|
|
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
|
-
];
|
|
8233
|
+
}
|
|
8234
|
+
}
|
|
8235
|
+
|
|
7427
8236
|
/** Maximum number of events to store in schedule reports */
|
|
7428
8237
|
const MAX_EVENTS$4 = 250;
|
|
7429
8238
|
/**
|
|
@@ -7568,9 +8377,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7568
8377
|
* Generates markdown report with all scheduled events for a strategy (View).
|
|
7569
8378
|
*
|
|
7570
8379
|
* @param strategyName - Strategy name
|
|
8380
|
+
* @param columns - Column configuration for formatting the table
|
|
7571
8381
|
* @returns Markdown formatted report with all events
|
|
7572
8382
|
*/
|
|
7573
|
-
async getReport(strategyName) {
|
|
8383
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.schedule_columns) {
|
|
7574
8384
|
const stats = await this.getData();
|
|
7575
8385
|
if (stats.totalEvents === 0) {
|
|
7576
8386
|
return [
|
|
@@ -7579,10 +8389,15 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7579
8389
|
"No scheduled signals recorded yet."
|
|
7580
8390
|
].join("\n");
|
|
7581
8391
|
}
|
|
7582
|
-
const visibleColumns =
|
|
8392
|
+
const visibleColumns = [];
|
|
8393
|
+
for (const col of columns) {
|
|
8394
|
+
if (await col.isVisible()) {
|
|
8395
|
+
visibleColumns.push(col);
|
|
8396
|
+
}
|
|
8397
|
+
}
|
|
7583
8398
|
const header = visibleColumns.map((col) => col.label);
|
|
7584
8399
|
const separator = visibleColumns.map(() => "---");
|
|
7585
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
8400
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
7586
8401
|
const tableData = [header, separator, ...rows];
|
|
7587
8402
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
7588
8403
|
return [
|
|
@@ -7605,9 +8420,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
7605
8420
|
*
|
|
7606
8421
|
* @param strategyName - Strategy name
|
|
7607
8422
|
* @param path - Directory path to save report (default: "./dump/schedule")
|
|
8423
|
+
* @param columns - Column configuration for formatting the table
|
|
7608
8424
|
*/
|
|
7609
|
-
async dump(strategyName, path$1 = "./dump/schedule") {
|
|
7610
|
-
const markdown = await this.getReport(strategyName);
|
|
8425
|
+
async dump(strategyName, path$1 = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
|
|
8426
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
7611
8427
|
try {
|
|
7612
8428
|
const dir = path.join(process.cwd(), path$1);
|
|
7613
8429
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -7713,6 +8529,7 @@ class ScheduleMarkdownService {
|
|
|
7713
8529
|
*
|
|
7714
8530
|
* @param symbol - Trading pair symbol
|
|
7715
8531
|
* @param strategyName - Strategy name to generate report for
|
|
8532
|
+
* @param columns - Column configuration for formatting the table
|
|
7716
8533
|
* @returns Markdown formatted report string with table of all events
|
|
7717
8534
|
*
|
|
7718
8535
|
* @example
|
|
@@ -7722,13 +8539,13 @@ class ScheduleMarkdownService {
|
|
|
7722
8539
|
* console.log(markdown);
|
|
7723
8540
|
* ```
|
|
7724
8541
|
*/
|
|
7725
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8542
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.schedule_columns) => {
|
|
7726
8543
|
this.loggerService.log("scheduleMarkdownService getReport", {
|
|
7727
8544
|
symbol,
|
|
7728
8545
|
strategyName,
|
|
7729
8546
|
});
|
|
7730
8547
|
const storage = this.getStorage(symbol, strategyName);
|
|
7731
|
-
return storage.getReport(strategyName);
|
|
8548
|
+
return storage.getReport(strategyName, columns);
|
|
7732
8549
|
};
|
|
7733
8550
|
/**
|
|
7734
8551
|
* Saves symbol-strategy report to disk.
|
|
@@ -7738,6 +8555,7 @@ class ScheduleMarkdownService {
|
|
|
7738
8555
|
* @param symbol - Trading pair symbol
|
|
7739
8556
|
* @param strategyName - Strategy name to save report for
|
|
7740
8557
|
* @param path - Directory path to save report (default: "./dump/schedule")
|
|
8558
|
+
* @param columns - Column configuration for formatting the table
|
|
7741
8559
|
*
|
|
7742
8560
|
* @example
|
|
7743
8561
|
* ```typescript
|
|
@@ -7750,14 +8568,14 @@ class ScheduleMarkdownService {
|
|
|
7750
8568
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
7751
8569
|
* ```
|
|
7752
8570
|
*/
|
|
7753
|
-
this.dump = async (symbol, strategyName, path = "./dump/schedule") => {
|
|
8571
|
+
this.dump = async (symbol, strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) => {
|
|
7754
8572
|
this.loggerService.log("scheduleMarkdownService dump", {
|
|
7755
8573
|
symbol,
|
|
7756
8574
|
strategyName,
|
|
7757
8575
|
path,
|
|
7758
8576
|
});
|
|
7759
8577
|
const storage = this.getStorage(symbol, strategyName);
|
|
7760
|
-
await storage.dump(strategyName, path);
|
|
8578
|
+
await storage.dump(strategyName, path, columns);
|
|
7761
8579
|
};
|
|
7762
8580
|
/**
|
|
7763
8581
|
* Clears accumulated event data from storage.
|
|
@@ -7816,86 +8634,6 @@ function percentile(sortedArray, p) {
|
|
|
7816
8634
|
const index = Math.ceil((sortedArray.length * p) / 100) - 1;
|
|
7817
8635
|
return sortedArray[Math.max(0, index)];
|
|
7818
8636
|
}
|
|
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
8637
|
/** Maximum number of performance events to store per strategy */
|
|
7900
8638
|
const MAX_EVENTS$3 = 10000;
|
|
7901
8639
|
/**
|
|
@@ -7998,9 +8736,10 @@ class PerformanceStorage {
|
|
|
7998
8736
|
* Generates markdown report with performance statistics.
|
|
7999
8737
|
*
|
|
8000
8738
|
* @param strategyName - Strategy name
|
|
8739
|
+
* @param columns - Column configuration for formatting the table
|
|
8001
8740
|
* @returns Markdown formatted report
|
|
8002
8741
|
*/
|
|
8003
|
-
async getReport(strategyName) {
|
|
8742
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.performance_columns) {
|
|
8004
8743
|
const stats = await this.getData(strategyName);
|
|
8005
8744
|
if (stats.totalEvents === 0) {
|
|
8006
8745
|
return [
|
|
@@ -8012,10 +8751,15 @@ class PerformanceStorage {
|
|
|
8012
8751
|
// Sort metrics by total duration (descending) to show bottlenecks first
|
|
8013
8752
|
const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
|
|
8014
8753
|
// Generate summary table using Column interface
|
|
8015
|
-
const visibleColumns =
|
|
8754
|
+
const visibleColumns = [];
|
|
8755
|
+
for (const col of columns) {
|
|
8756
|
+
if (await col.isVisible()) {
|
|
8757
|
+
visibleColumns.push(col);
|
|
8758
|
+
}
|
|
8759
|
+
}
|
|
8016
8760
|
const header = visibleColumns.map((col) => col.label);
|
|
8017
8761
|
const separator = visibleColumns.map(() => "---");
|
|
8018
|
-
const rows = sortedMetrics.map((metric) => visibleColumns.map((col) => col.format(metric)));
|
|
8762
|
+
const rows = await Promise.all(sortedMetrics.map(async (metric, index) => Promise.all(visibleColumns.map((col) => col.format(metric, index)))));
|
|
8019
8763
|
const tableData = [header, separator, ...rows];
|
|
8020
8764
|
const summaryTable = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8021
8765
|
// Calculate percentage of total time for each metric
|
|
@@ -8046,9 +8790,10 @@ class PerformanceStorage {
|
|
|
8046
8790
|
*
|
|
8047
8791
|
* @param strategyName - Strategy name
|
|
8048
8792
|
* @param path - Directory path to save report
|
|
8793
|
+
* @param columns - Column configuration for formatting the table
|
|
8049
8794
|
*/
|
|
8050
|
-
async dump(strategyName, path$1 = "./dump/performance") {
|
|
8051
|
-
const markdown = await this.getReport(strategyName);
|
|
8795
|
+
async dump(strategyName, path$1 = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
|
|
8796
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
8052
8797
|
try {
|
|
8053
8798
|
const dir = path.join(process.cwd(), path$1);
|
|
8054
8799
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -8141,6 +8886,7 @@ class PerformanceMarkdownService {
|
|
|
8141
8886
|
*
|
|
8142
8887
|
* @param symbol - Trading pair symbol
|
|
8143
8888
|
* @param strategyName - Strategy name to generate report for
|
|
8889
|
+
* @param columns - Column configuration for formatting the table
|
|
8144
8890
|
* @returns Markdown formatted report string
|
|
8145
8891
|
*
|
|
8146
8892
|
* @example
|
|
@@ -8149,13 +8895,13 @@ class PerformanceMarkdownService {
|
|
|
8149
8895
|
* console.log(markdown);
|
|
8150
8896
|
* ```
|
|
8151
8897
|
*/
|
|
8152
|
-
this.getReport = async (symbol, strategyName) => {
|
|
8898
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.performance_columns) => {
|
|
8153
8899
|
this.loggerService.log("performanceMarkdownService getReport", {
|
|
8154
8900
|
symbol,
|
|
8155
8901
|
strategyName,
|
|
8156
8902
|
});
|
|
8157
8903
|
const storage = this.getStorage(symbol, strategyName);
|
|
8158
|
-
return storage.getReport(strategyName);
|
|
8904
|
+
return storage.getReport(strategyName, columns);
|
|
8159
8905
|
};
|
|
8160
8906
|
/**
|
|
8161
8907
|
* Saves performance report to disk.
|
|
@@ -8163,6 +8909,7 @@ class PerformanceMarkdownService {
|
|
|
8163
8909
|
* @param symbol - Trading pair symbol
|
|
8164
8910
|
* @param strategyName - Strategy name to save report for
|
|
8165
8911
|
* @param path - Directory path to save report
|
|
8912
|
+
* @param columns - Column configuration for formatting the table
|
|
8166
8913
|
*
|
|
8167
8914
|
* @example
|
|
8168
8915
|
* ```typescript
|
|
@@ -8173,14 +8920,14 @@ class PerformanceMarkdownService {
|
|
|
8173
8920
|
* await performanceService.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
8174
8921
|
* ```
|
|
8175
8922
|
*/
|
|
8176
|
-
this.dump = async (symbol, strategyName, path = "./dump/performance") => {
|
|
8923
|
+
this.dump = async (symbol, strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) => {
|
|
8177
8924
|
this.loggerService.log("performanceMarkdownService dump", {
|
|
8178
8925
|
symbol,
|
|
8179
8926
|
strategyName,
|
|
8180
8927
|
path,
|
|
8181
8928
|
});
|
|
8182
8929
|
const storage = this.getStorage(symbol, strategyName);
|
|
8183
|
-
await storage.dump(strategyName, path);
|
|
8930
|
+
await storage.dump(strategyName, path, columns);
|
|
8184
8931
|
};
|
|
8185
8932
|
/**
|
|
8186
8933
|
* Clears accumulated performance data from storage.
|
|
@@ -8239,135 +8986,6 @@ function formatMetric(value) {
|
|
|
8239
8986
|
}
|
|
8240
8987
|
return value.toFixed(2);
|
|
8241
8988
|
}
|
|
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
8989
|
/**
|
|
8372
8990
|
* Storage class for accumulating walker results.
|
|
8373
8991
|
* Maintains a list of all strategy results and provides methods to generate reports.
|
|
@@ -8399,7 +9017,7 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8399
9017
|
this._strategyResults.unshift({
|
|
8400
9018
|
strategyName: data.strategyName,
|
|
8401
9019
|
stats: data.stats,
|
|
8402
|
-
metricValue: data.metricValue,
|
|
9020
|
+
metricValue: isUnsafe$1(data.metricValue) ? null : data.metricValue,
|
|
8403
9021
|
});
|
|
8404
9022
|
}
|
|
8405
9023
|
/**
|
|
@@ -8432,11 +9050,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8432
9050
|
* Generates comparison table for top N strategies (View).
|
|
8433
9051
|
* Sorts strategies by metric value and formats as markdown table.
|
|
8434
9052
|
*
|
|
8435
|
-
* @param metric - Metric being optimized
|
|
8436
9053
|
* @param topN - Number of top strategies to include (default: 10)
|
|
9054
|
+
* @param columns - Column configuration for formatting the strategy comparison table
|
|
8437
9055
|
* @returns Markdown formatted comparison table
|
|
8438
9056
|
*/
|
|
8439
|
-
getComparisonTable(
|
|
9057
|
+
async getComparisonTable(topN = 10, columns = COLUMN_CONFIG.walker_strategy_columns) {
|
|
8440
9058
|
if (this._strategyResults.length === 0) {
|
|
8441
9059
|
return "No strategy results available.";
|
|
8442
9060
|
}
|
|
@@ -8449,13 +9067,17 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8449
9067
|
// Take top N strategies
|
|
8450
9068
|
const topStrategies = sortedResults.slice(0, topN);
|
|
8451
9069
|
// Get columns configuration
|
|
8452
|
-
const
|
|
8453
|
-
const
|
|
9070
|
+
const visibleColumns = [];
|
|
9071
|
+
for (const col of columns) {
|
|
9072
|
+
if (await col.isVisible()) {
|
|
9073
|
+
visibleColumns.push(col);
|
|
9074
|
+
}
|
|
9075
|
+
}
|
|
8454
9076
|
// Build table header
|
|
8455
9077
|
const header = visibleColumns.map((col) => col.label);
|
|
8456
9078
|
const separator = visibleColumns.map(() => "---");
|
|
8457
9079
|
// Build table rows
|
|
8458
|
-
const rows = topStrategies.map((result, index) => visibleColumns.map((col) => col.format(result, index)));
|
|
9080
|
+
const rows = await Promise.all(topStrategies.map(async (result, index) => Promise.all(visibleColumns.map((col) => col.format(result, index)))));
|
|
8459
9081
|
const tableData = [header, separator, ...rows];
|
|
8460
9082
|
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8461
9083
|
}
|
|
@@ -8463,9 +9085,10 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8463
9085
|
* Generates PNL table showing all closed signals across all strategies (View).
|
|
8464
9086
|
* Collects all signals from all strategies and formats as markdown table.
|
|
8465
9087
|
*
|
|
9088
|
+
* @param columns - Column configuration for formatting the PNL table
|
|
8466
9089
|
* @returns Markdown formatted PNL table
|
|
8467
9090
|
*/
|
|
8468
|
-
getPnlTable() {
|
|
9091
|
+
async getPnlTable(columns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
8469
9092
|
if (this._strategyResults.length === 0) {
|
|
8470
9093
|
return "No strategy results available.";
|
|
8471
9094
|
}
|
|
@@ -8489,11 +9112,16 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8489
9112
|
return "No closed signals available.";
|
|
8490
9113
|
}
|
|
8491
9114
|
// Build table header
|
|
8492
|
-
const visibleColumns =
|
|
9115
|
+
const visibleColumns = [];
|
|
9116
|
+
for (const col of columns) {
|
|
9117
|
+
if (await col.isVisible()) {
|
|
9118
|
+
visibleColumns.push(col);
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
8493
9121
|
const header = visibleColumns.map((col) => col.label);
|
|
8494
9122
|
const separator = visibleColumns.map(() => "---");
|
|
8495
9123
|
// Build table rows
|
|
8496
|
-
const rows = allSignals.map((signal) => visibleColumns.map((col) => col.format(signal)));
|
|
9124
|
+
const rows = await Promise.all(allSignals.map(async (signal, index) => Promise.all(visibleColumns.map((col) => col.format(signal, index)))));
|
|
8497
9125
|
const tableData = [header, separator, ...rows];
|
|
8498
9126
|
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8499
9127
|
}
|
|
@@ -8504,9 +9132,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8504
9132
|
* @param symbol - Trading symbol
|
|
8505
9133
|
* @param metric - Metric being optimized
|
|
8506
9134
|
* @param context - Context with exchangeName and frameName
|
|
9135
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9136
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8507
9137
|
* @returns Markdown formatted report with all results
|
|
8508
9138
|
*/
|
|
8509
|
-
async getReport(symbol, metric, context) {
|
|
9139
|
+
async getReport(symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
8510
9140
|
const results = await this.getData(symbol, metric, context);
|
|
8511
9141
|
// Get total signals for best strategy
|
|
8512
9142
|
const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
|
|
@@ -8526,11 +9156,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8526
9156
|
"",
|
|
8527
9157
|
"## Top Strategies Comparison",
|
|
8528
9158
|
"",
|
|
8529
|
-
this.getComparisonTable(
|
|
9159
|
+
await this.getComparisonTable(10, strategyColumns),
|
|
8530
9160
|
"",
|
|
8531
9161
|
"## All Signals (PNL Table)",
|
|
8532
9162
|
"",
|
|
8533
|
-
this.getPnlTable(),
|
|
9163
|
+
await this.getPnlTable(pnlColumns),
|
|
8534
9164
|
"",
|
|
8535
9165
|
"**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
|
|
8536
9166
|
].join("\n");
|
|
@@ -8542,9 +9172,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
8542
9172
|
* @param metric - Metric being optimized
|
|
8543
9173
|
* @param context - Context with exchangeName and frameName
|
|
8544
9174
|
* @param path - Directory path to save report (default: "./dump/walker")
|
|
9175
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9176
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8545
9177
|
*/
|
|
8546
|
-
async dump(symbol, metric, context, path$1 = "./dump/walker") {
|
|
8547
|
-
const markdown = await this.getReport(symbol, metric, context);
|
|
9178
|
+
async dump(symbol, metric, context, path$1 = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
|
|
9179
|
+
const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
8548
9180
|
try {
|
|
8549
9181
|
const dir = path.join(process.cwd(), path$1);
|
|
8550
9182
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -8637,6 +9269,8 @@ class WalkerMarkdownService {
|
|
|
8637
9269
|
* @param symbol - Trading symbol
|
|
8638
9270
|
* @param metric - Metric being optimized
|
|
8639
9271
|
* @param context - Context with exchangeName and frameName
|
|
9272
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9273
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8640
9274
|
* @returns Markdown formatted report string
|
|
8641
9275
|
*
|
|
8642
9276
|
* @example
|
|
@@ -8646,7 +9280,7 @@ class WalkerMarkdownService {
|
|
|
8646
9280
|
* console.log(markdown);
|
|
8647
9281
|
* ```
|
|
8648
9282
|
*/
|
|
8649
|
-
this.getReport = async (walkerName, symbol, metric, context) => {
|
|
9283
|
+
this.getReport = async (walkerName, symbol, metric, context, strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
|
|
8650
9284
|
this.loggerService.log("walkerMarkdownService getReport", {
|
|
8651
9285
|
walkerName,
|
|
8652
9286
|
symbol,
|
|
@@ -8654,7 +9288,7 @@ class WalkerMarkdownService {
|
|
|
8654
9288
|
context,
|
|
8655
9289
|
});
|
|
8656
9290
|
const storage = this.getStorage(walkerName);
|
|
8657
|
-
return storage.getReport(symbol, metric, context);
|
|
9291
|
+
return storage.getReport(symbol, metric, context, strategyColumns, pnlColumns);
|
|
8658
9292
|
};
|
|
8659
9293
|
/**
|
|
8660
9294
|
* Saves walker report to disk.
|
|
@@ -8666,6 +9300,8 @@ class WalkerMarkdownService {
|
|
|
8666
9300
|
* @param metric - Metric being optimized
|
|
8667
9301
|
* @param context - Context with exchangeName and frameName
|
|
8668
9302
|
* @param path - Directory path to save report (default: "./dump/walker")
|
|
9303
|
+
* @param strategyColumns - Column configuration for strategy comparison table
|
|
9304
|
+
* @param pnlColumns - Column configuration for PNL table
|
|
8669
9305
|
*
|
|
8670
9306
|
* @example
|
|
8671
9307
|
* ```typescript
|
|
@@ -8678,7 +9314,7 @@ class WalkerMarkdownService {
|
|
|
8678
9314
|
* await service.dump("my-walker", "BTCUSDT", "sharpeRatio", { exchangeName: "binance", frameName: "1d" }, "./custom/path");
|
|
8679
9315
|
* ```
|
|
8680
9316
|
*/
|
|
8681
|
-
this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker") => {
|
|
9317
|
+
this.dump = async (walkerName, symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) => {
|
|
8682
9318
|
this.loggerService.log("walkerMarkdownService dump", {
|
|
8683
9319
|
walkerName,
|
|
8684
9320
|
symbol,
|
|
@@ -8687,7 +9323,7 @@ class WalkerMarkdownService {
|
|
|
8687
9323
|
path,
|
|
8688
9324
|
});
|
|
8689
9325
|
const storage = this.getStorage(walkerName);
|
|
8690
|
-
await storage.dump(symbol, metric, context, path);
|
|
9326
|
+
await storage.dump(symbol, metric, context, path, strategyColumns, pnlColumns);
|
|
8691
9327
|
};
|
|
8692
9328
|
/**
|
|
8693
9329
|
* Clears accumulated result data from storage.
|
|
@@ -8734,99 +9370,25 @@ class WalkerMarkdownService {
|
|
|
8734
9370
|
const HEATMAP_METHOD_NAME_GET_DATA = "HeatMarkdownService.getData";
|
|
8735
9371
|
const HEATMAP_METHOD_NAME_GET_REPORT = "HeatMarkdownService.getReport";
|
|
8736
9372
|
const HEATMAP_METHOD_NAME_DUMP = "HeatMarkdownService.dump";
|
|
8737
|
-
const HEATMAP_METHOD_NAME_CLEAR = "HeatMarkdownService.clear";
|
|
8738
|
-
/**
|
|
8739
|
-
* Checks if a value is unsafe for display (not a number, NaN, or Infinity).
|
|
8740
|
-
*
|
|
8741
|
-
* @param value - Value to check
|
|
8742
|
-
* @returns true if value is unsafe, false otherwise
|
|
8743
|
-
*/
|
|
8744
|
-
function isUnsafe(value) {
|
|
8745
|
-
if (typeof value !== "number") {
|
|
8746
|
-
return true;
|
|
8747
|
-
}
|
|
8748
|
-
if (isNaN(value)) {
|
|
8749
|
-
return true;
|
|
8750
|
-
}
|
|
8751
|
-
if (!isFinite(value)) {
|
|
8752
|
-
return true;
|
|
8753
|
-
}
|
|
8754
|
-
return false;
|
|
8755
|
-
}
|
|
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
|
-
];
|
|
9373
|
+
const HEATMAP_METHOD_NAME_CLEAR = "HeatMarkdownService.clear";
|
|
9374
|
+
/**
|
|
9375
|
+
* Checks if a value is unsafe for display (not a number, NaN, or Infinity).
|
|
9376
|
+
*
|
|
9377
|
+
* @param value - Value to check
|
|
9378
|
+
* @returns true if value is unsafe, false otherwise
|
|
9379
|
+
*/
|
|
9380
|
+
function isUnsafe(value) {
|
|
9381
|
+
if (typeof value !== "number") {
|
|
9382
|
+
return true;
|
|
9383
|
+
}
|
|
9384
|
+
if (isNaN(value)) {
|
|
9385
|
+
return true;
|
|
9386
|
+
}
|
|
9387
|
+
if (!isFinite(value)) {
|
|
9388
|
+
return true;
|
|
9389
|
+
}
|
|
9390
|
+
return false;
|
|
9391
|
+
}
|
|
8830
9392
|
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
8831
9393
|
const MAX_EVENTS$2 = 250;
|
|
8832
9394
|
/**
|
|
@@ -9061,9 +9623,10 @@ class HeatmapStorage {
|
|
|
9061
9623
|
* Generates markdown report with portfolio heatmap table (View).
|
|
9062
9624
|
*
|
|
9063
9625
|
* @param strategyName - Strategy name for report title
|
|
9626
|
+
* @param columns - Column configuration for formatting the table
|
|
9064
9627
|
* @returns Promise resolving to markdown formatted report string
|
|
9065
9628
|
*/
|
|
9066
|
-
async getReport(strategyName) {
|
|
9629
|
+
async getReport(strategyName, columns = COLUMN_CONFIG.heat_columns) {
|
|
9067
9630
|
const data = await this.getData();
|
|
9068
9631
|
if (data.symbols.length === 0) {
|
|
9069
9632
|
return [
|
|
@@ -9072,10 +9635,15 @@ class HeatmapStorage {
|
|
|
9072
9635
|
"*No data available*"
|
|
9073
9636
|
].join("\n");
|
|
9074
9637
|
}
|
|
9075
|
-
const visibleColumns =
|
|
9638
|
+
const visibleColumns = [];
|
|
9639
|
+
for (const col of columns) {
|
|
9640
|
+
if (await col.isVisible()) {
|
|
9641
|
+
visibleColumns.push(col);
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9076
9644
|
const header = visibleColumns.map((col) => col.label);
|
|
9077
9645
|
const separator = visibleColumns.map(() => "---");
|
|
9078
|
-
const rows = data.symbols.map((row) => visibleColumns.map((col) => col.format(row)));
|
|
9646
|
+
const rows = await Promise.all(data.symbols.map(async (row, index) => Promise.all(visibleColumns.map((col) => col.format(row, index)))));
|
|
9079
9647
|
const tableData = [header, separator, ...rows];
|
|
9080
9648
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
9081
9649
|
return [
|
|
@@ -9091,9 +9659,10 @@ class HeatmapStorage {
|
|
|
9091
9659
|
*
|
|
9092
9660
|
* @param strategyName - Strategy name for filename
|
|
9093
9661
|
* @param path - Directory path to save report (default: "./dump/heatmap")
|
|
9662
|
+
* @param columns - Column configuration for formatting the table
|
|
9094
9663
|
*/
|
|
9095
|
-
async dump(strategyName, path$1 = "./dump/heatmap") {
|
|
9096
|
-
const markdown = await this.getReport(strategyName);
|
|
9664
|
+
async dump(strategyName, path$1 = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
|
|
9665
|
+
const markdown = await this.getReport(strategyName, columns);
|
|
9097
9666
|
try {
|
|
9098
9667
|
const dir = path.join(process.cwd(), path$1);
|
|
9099
9668
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -9190,6 +9759,7 @@ class HeatMarkdownService {
|
|
|
9190
9759
|
* Generates markdown report with portfolio heatmap table for a strategy.
|
|
9191
9760
|
*
|
|
9192
9761
|
* @param strategyName - Strategy name to generate heatmap report for
|
|
9762
|
+
* @param columns - Column configuration for formatting the table
|
|
9193
9763
|
* @returns Promise resolving to markdown formatted report string
|
|
9194
9764
|
*
|
|
9195
9765
|
* @example
|
|
@@ -9209,12 +9779,12 @@ class HeatMarkdownService {
|
|
|
9209
9779
|
* // ...
|
|
9210
9780
|
* ```
|
|
9211
9781
|
*/
|
|
9212
|
-
this.getReport = async (strategyName) => {
|
|
9782
|
+
this.getReport = async (strategyName, columns = COLUMN_CONFIG.heat_columns) => {
|
|
9213
9783
|
this.loggerService.log(HEATMAP_METHOD_NAME_GET_REPORT, {
|
|
9214
9784
|
strategyName,
|
|
9215
9785
|
});
|
|
9216
9786
|
const storage = this.getStorage(strategyName);
|
|
9217
|
-
return storage.getReport(strategyName);
|
|
9787
|
+
return storage.getReport(strategyName, columns);
|
|
9218
9788
|
};
|
|
9219
9789
|
/**
|
|
9220
9790
|
* Saves heatmap report to disk for a strategy.
|
|
@@ -9224,6 +9794,7 @@ class HeatMarkdownService {
|
|
|
9224
9794
|
*
|
|
9225
9795
|
* @param strategyName - Strategy name to save heatmap report for
|
|
9226
9796
|
* @param path - Optional directory path to save report (default: "./dump/heatmap")
|
|
9797
|
+
* @param columns - Column configuration for formatting the table
|
|
9227
9798
|
*
|
|
9228
9799
|
* @example
|
|
9229
9800
|
* ```typescript
|
|
@@ -9236,13 +9807,13 @@ class HeatMarkdownService {
|
|
|
9236
9807
|
* await service.dump("my-strategy", "./reports");
|
|
9237
9808
|
* ```
|
|
9238
9809
|
*/
|
|
9239
|
-
this.dump = async (strategyName, path = "./dump/heatmap") => {
|
|
9810
|
+
this.dump = async (strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) => {
|
|
9240
9811
|
this.loggerService.log(HEATMAP_METHOD_NAME_DUMP, {
|
|
9241
9812
|
strategyName,
|
|
9242
9813
|
path,
|
|
9243
9814
|
});
|
|
9244
9815
|
const storage = this.getStorage(strategyName);
|
|
9245
|
-
await storage.dump(strategyName, path);
|
|
9816
|
+
await storage.dump(strategyName, path, columns);
|
|
9246
9817
|
};
|
|
9247
9818
|
/**
|
|
9248
9819
|
* Clears accumulated heatmap data from storage.
|
|
@@ -11633,62 +12204,6 @@ class PartialConnectionService {
|
|
|
11633
12204
|
}
|
|
11634
12205
|
}
|
|
11635
12206
|
|
|
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
12207
|
/** Maximum number of events to store in partial reports */
|
|
11693
12208
|
const MAX_EVENTS$1 = 250;
|
|
11694
12209
|
/**
|
|
@@ -11778,9 +12293,10 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11778
12293
|
*
|
|
11779
12294
|
* @param symbol - Trading pair symbol
|
|
11780
12295
|
* @param strategyName - Strategy name
|
|
12296
|
+
* @param columns - Column configuration for formatting the table
|
|
11781
12297
|
* @returns Markdown formatted report with all events
|
|
11782
12298
|
*/
|
|
11783
|
-
async getReport(symbol, strategyName) {
|
|
12299
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) {
|
|
11784
12300
|
const stats = await this.getData();
|
|
11785
12301
|
if (stats.totalEvents === 0) {
|
|
11786
12302
|
return [
|
|
@@ -11789,10 +12305,15 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11789
12305
|
"No partial profit/loss events recorded yet."
|
|
11790
12306
|
].join("\n");
|
|
11791
12307
|
}
|
|
11792
|
-
const visibleColumns =
|
|
12308
|
+
const visibleColumns = [];
|
|
12309
|
+
for (const col of columns) {
|
|
12310
|
+
if (await col.isVisible()) {
|
|
12311
|
+
visibleColumns.push(col);
|
|
12312
|
+
}
|
|
12313
|
+
}
|
|
11793
12314
|
const header = visibleColumns.map((col) => col.label);
|
|
11794
12315
|
const separator = visibleColumns.map(() => "---");
|
|
11795
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
12316
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
11796
12317
|
const tableData = [header, separator, ...rows];
|
|
11797
12318
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
11798
12319
|
return [
|
|
@@ -11811,9 +12332,10 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
11811
12332
|
* @param symbol - Trading pair symbol
|
|
11812
12333
|
* @param strategyName - Strategy name
|
|
11813
12334
|
* @param path - Directory path to save report (default: "./dump/partial")
|
|
12335
|
+
* @param columns - Column configuration for formatting the table
|
|
11814
12336
|
*/
|
|
11815
|
-
async dump(symbol, strategyName, path$1 = "./dump/partial") {
|
|
11816
|
-
const markdown = await this.getReport(symbol, strategyName);
|
|
12337
|
+
async dump(symbol, strategyName, path$1 = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
|
|
12338
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
11817
12339
|
try {
|
|
11818
12340
|
const dir = path.join(process.cwd(), path$1);
|
|
11819
12341
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -11924,6 +12446,7 @@ class PartialMarkdownService {
|
|
|
11924
12446
|
*
|
|
11925
12447
|
* @param symbol - Trading pair symbol to generate report for
|
|
11926
12448
|
* @param strategyName - Strategy name to generate report for
|
|
12449
|
+
* @param columns - Column configuration for formatting the table
|
|
11927
12450
|
* @returns Markdown formatted report string with table of all events
|
|
11928
12451
|
*
|
|
11929
12452
|
* @example
|
|
@@ -11933,13 +12456,13 @@ class PartialMarkdownService {
|
|
|
11933
12456
|
* console.log(markdown);
|
|
11934
12457
|
* ```
|
|
11935
12458
|
*/
|
|
11936
|
-
this.getReport = async (symbol, strategyName) => {
|
|
12459
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.partial_columns) => {
|
|
11937
12460
|
this.loggerService.log("partialMarkdownService getReport", {
|
|
11938
12461
|
symbol,
|
|
11939
12462
|
strategyName,
|
|
11940
12463
|
});
|
|
11941
12464
|
const storage = this.getStorage(symbol, strategyName);
|
|
11942
|
-
return storage.getReport(symbol, strategyName);
|
|
12465
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
11943
12466
|
};
|
|
11944
12467
|
/**
|
|
11945
12468
|
* Saves symbol-strategy report to disk.
|
|
@@ -11949,6 +12472,7 @@ class PartialMarkdownService {
|
|
|
11949
12472
|
* @param symbol - Trading pair symbol to save report for
|
|
11950
12473
|
* @param strategyName - Strategy name to save report for
|
|
11951
12474
|
* @param path - Directory path to save report (default: "./dump/partial")
|
|
12475
|
+
* @param columns - Column configuration for formatting the table
|
|
11952
12476
|
*
|
|
11953
12477
|
* @example
|
|
11954
12478
|
* ```typescript
|
|
@@ -11961,14 +12485,14 @@ class PartialMarkdownService {
|
|
|
11961
12485
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
11962
12486
|
* ```
|
|
11963
12487
|
*/
|
|
11964
|
-
this.dump = async (symbol, strategyName, path = "./dump/partial") => {
|
|
12488
|
+
this.dump = async (symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) => {
|
|
11965
12489
|
this.loggerService.log("partialMarkdownService dump", {
|
|
11966
12490
|
symbol,
|
|
11967
12491
|
strategyName,
|
|
11968
12492
|
path,
|
|
11969
12493
|
});
|
|
11970
12494
|
const storage = this.getStorage(symbol, strategyName);
|
|
11971
|
-
await storage.dump(symbol, strategyName, path);
|
|
12495
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
11972
12496
|
};
|
|
11973
12497
|
/**
|
|
11974
12498
|
* Clears accumulated event data from storage.
|
|
@@ -12428,92 +12952,6 @@ class ConfigValidationService {
|
|
|
12428
12952
|
}
|
|
12429
12953
|
}
|
|
12430
12954
|
|
|
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
12955
|
/** Maximum number of events to store in risk reports */
|
|
12518
12956
|
const MAX_EVENTS = 250;
|
|
12519
12957
|
/**
|
|
@@ -12569,9 +13007,10 @@ class ReportStorage {
|
|
|
12569
13007
|
*
|
|
12570
13008
|
* @param symbol - Trading pair symbol
|
|
12571
13009
|
* @param strategyName - Strategy name
|
|
13010
|
+
* @param columns - Column configuration for formatting the table
|
|
12572
13011
|
* @returns Markdown formatted report with all events
|
|
12573
13012
|
*/
|
|
12574
|
-
async getReport(symbol, strategyName) {
|
|
13013
|
+
async getReport(symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) {
|
|
12575
13014
|
const stats = await this.getData();
|
|
12576
13015
|
if (stats.totalRejections === 0) {
|
|
12577
13016
|
return [
|
|
@@ -12580,10 +13019,15 @@ class ReportStorage {
|
|
|
12580
13019
|
"No risk rejections recorded yet.",
|
|
12581
13020
|
].join("\n");
|
|
12582
13021
|
}
|
|
12583
|
-
const visibleColumns =
|
|
13022
|
+
const visibleColumns = [];
|
|
13023
|
+
for (const col of columns) {
|
|
13024
|
+
if (await col.isVisible()) {
|
|
13025
|
+
visibleColumns.push(col);
|
|
13026
|
+
}
|
|
13027
|
+
}
|
|
12584
13028
|
const header = visibleColumns.map((col) => col.label);
|
|
12585
13029
|
const separator = visibleColumns.map(() => "---");
|
|
12586
|
-
const rows = this._eventList.map((event) => visibleColumns.map((col) => col.format(event)));
|
|
13030
|
+
const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
|
|
12587
13031
|
const tableData = [header, separator, ...rows];
|
|
12588
13032
|
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
12589
13033
|
return [
|
|
@@ -12606,9 +13050,10 @@ class ReportStorage {
|
|
|
12606
13050
|
* @param symbol - Trading pair symbol
|
|
12607
13051
|
* @param strategyName - Strategy name
|
|
12608
13052
|
* @param path - Directory path to save report (default: "./dump/risk")
|
|
13053
|
+
* @param columns - Column configuration for formatting the table
|
|
12609
13054
|
*/
|
|
12610
|
-
async dump(symbol, strategyName, path$1 = "./dump/risk") {
|
|
12611
|
-
const markdown = await this.getReport(symbol, strategyName);
|
|
13055
|
+
async dump(symbol, strategyName, path$1 = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
|
|
13056
|
+
const markdown = await this.getReport(symbol, strategyName, columns);
|
|
12612
13057
|
try {
|
|
12613
13058
|
const dir = path.join(process.cwd(), path$1);
|
|
12614
13059
|
await fs.mkdir(dir, { recursive: true });
|
|
@@ -12700,6 +13145,7 @@ class RiskMarkdownService {
|
|
|
12700
13145
|
*
|
|
12701
13146
|
* @param symbol - Trading pair symbol to generate report for
|
|
12702
13147
|
* @param strategyName - Strategy name to generate report for
|
|
13148
|
+
* @param columns - Column configuration for formatting the table
|
|
12703
13149
|
* @returns Markdown formatted report string with table of all events
|
|
12704
13150
|
*
|
|
12705
13151
|
* @example
|
|
@@ -12709,13 +13155,13 @@ class RiskMarkdownService {
|
|
|
12709
13155
|
* console.log(markdown);
|
|
12710
13156
|
* ```
|
|
12711
13157
|
*/
|
|
12712
|
-
this.getReport = async (symbol, strategyName) => {
|
|
13158
|
+
this.getReport = async (symbol, strategyName, columns = COLUMN_CONFIG.risk_columns) => {
|
|
12713
13159
|
this.loggerService.log("riskMarkdownService getReport", {
|
|
12714
13160
|
symbol,
|
|
12715
13161
|
strategyName,
|
|
12716
13162
|
});
|
|
12717
13163
|
const storage = this.getStorage(symbol, strategyName);
|
|
12718
|
-
return storage.getReport(symbol, strategyName);
|
|
13164
|
+
return storage.getReport(symbol, strategyName, columns);
|
|
12719
13165
|
};
|
|
12720
13166
|
/**
|
|
12721
13167
|
* Saves symbol-strategy report to disk.
|
|
@@ -12725,6 +13171,7 @@ class RiskMarkdownService {
|
|
|
12725
13171
|
* @param symbol - Trading pair symbol to save report for
|
|
12726
13172
|
* @param strategyName - Strategy name to save report for
|
|
12727
13173
|
* @param path - Directory path to save report (default: "./dump/risk")
|
|
13174
|
+
* @param columns - Column configuration for formatting the table
|
|
12728
13175
|
*
|
|
12729
13176
|
* @example
|
|
12730
13177
|
* ```typescript
|
|
@@ -12737,14 +13184,14 @@ class RiskMarkdownService {
|
|
|
12737
13184
|
* await service.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
12738
13185
|
* ```
|
|
12739
13186
|
*/
|
|
12740
|
-
this.dump = async (symbol, strategyName, path = "./dump/risk") => {
|
|
13187
|
+
this.dump = async (symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) => {
|
|
12741
13188
|
this.loggerService.log("riskMarkdownService dump", {
|
|
12742
13189
|
symbol,
|
|
12743
13190
|
strategyName,
|
|
12744
13191
|
path,
|
|
12745
13192
|
});
|
|
12746
13193
|
const storage = this.getStorage(symbol, strategyName);
|
|
12747
|
-
await storage.dump(symbol, strategyName, path);
|
|
13194
|
+
await storage.dump(symbol, strategyName, path, columns);
|
|
12748
13195
|
};
|
|
12749
13196
|
/**
|
|
12750
13197
|
* Clears accumulated event data from storage.
|
|
@@ -12794,6 +13241,118 @@ class RiskMarkdownService {
|
|
|
12794
13241
|
}
|
|
12795
13242
|
}
|
|
12796
13243
|
|
|
13244
|
+
/**
|
|
13245
|
+
* Service for validating column configurations to ensure consistency with ColumnModel interface
|
|
13246
|
+
* and prevent invalid column definitions.
|
|
13247
|
+
*
|
|
13248
|
+
* Performs comprehensive validation on all column definitions in COLUMN_CONFIG:
|
|
13249
|
+
* - **Required fields**: All columns must have key, label, format, and isVisible properties
|
|
13250
|
+
* - **Unique keys**: All key values must be unique within each column collection
|
|
13251
|
+
* - **Function validation**: format and isVisible must be callable functions
|
|
13252
|
+
* - **Data types**: key and label must be non-empty strings
|
|
13253
|
+
*
|
|
13254
|
+
* @throws {Error} If any validation fails, throws with detailed breakdown of all errors
|
|
13255
|
+
*
|
|
13256
|
+
* @example
|
|
13257
|
+
* ```typescript
|
|
13258
|
+
* const validator = new ColumnValidationService();
|
|
13259
|
+
* validator.validate(); // Throws if column configuration is invalid
|
|
13260
|
+
* ```
|
|
13261
|
+
*
|
|
13262
|
+
* @example Validation failure output:
|
|
13263
|
+
* ```
|
|
13264
|
+
* Column configuration validation failed:
|
|
13265
|
+
* 1. backtest_columns[0]: Missing required field "format"
|
|
13266
|
+
* 2. heat_columns: Duplicate key "symbol" at indexes 1, 5
|
|
13267
|
+
* 3. live_columns[3].isVisible must be a function, got "boolean"
|
|
13268
|
+
* ```
|
|
13269
|
+
*/
|
|
13270
|
+
class ColumnValidationService {
|
|
13271
|
+
constructor() {
|
|
13272
|
+
/**
|
|
13273
|
+
* @private
|
|
13274
|
+
* @readonly
|
|
13275
|
+
* Injected logger service instance
|
|
13276
|
+
*/
|
|
13277
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
13278
|
+
/**
|
|
13279
|
+
* Validates all column configurations in COLUMN_CONFIG for structural correctness.
|
|
13280
|
+
*
|
|
13281
|
+
* Checks:
|
|
13282
|
+
* 1. All required fields (key, label, format, isVisible) are present in each column
|
|
13283
|
+
* 2. key and label are non-empty strings
|
|
13284
|
+
* 3. format and isVisible are functions (not other types)
|
|
13285
|
+
* 4. All keys are unique within each column collection
|
|
13286
|
+
*
|
|
13287
|
+
* @throws Error if configuration is invalid
|
|
13288
|
+
*/
|
|
13289
|
+
this.validate = () => {
|
|
13290
|
+
this.loggerService.log("columnValidationService validate");
|
|
13291
|
+
const errors = [];
|
|
13292
|
+
// Iterate through all column collections in COLUMN_CONFIG
|
|
13293
|
+
for (const [configKey, columns] of Object.entries(COLUMN_CONFIG)) {
|
|
13294
|
+
if (!Array.isArray(columns)) {
|
|
13295
|
+
errors.push(`${configKey} is not an array, got ${typeof columns}`);
|
|
13296
|
+
continue;
|
|
13297
|
+
}
|
|
13298
|
+
// Track keys for uniqueness check
|
|
13299
|
+
const keyMap = new Map();
|
|
13300
|
+
// Validate each column in the collection
|
|
13301
|
+
columns.forEach((column, index) => {
|
|
13302
|
+
if (!column || typeof column !== "object") {
|
|
13303
|
+
errors.push(`${configKey}[${index}]: Column must be an object, got ${typeof column}`);
|
|
13304
|
+
return;
|
|
13305
|
+
}
|
|
13306
|
+
// Check for all required fields
|
|
13307
|
+
const requiredFields = ["key", "label", "format", "isVisible"];
|
|
13308
|
+
for (const field of requiredFields) {
|
|
13309
|
+
if (!(field in column)) {
|
|
13310
|
+
errors.push(`${configKey}[${index}]: Missing required field "${field}"`);
|
|
13311
|
+
}
|
|
13312
|
+
}
|
|
13313
|
+
// Validate key and label are non-empty strings
|
|
13314
|
+
if (typeof column.key !== "string" || column.key.trim() === "") {
|
|
13315
|
+
errors.push(`${configKey}[${index}].key must be a non-empty string, got ${typeof column.key === "string" ? `"${column.key}"` : typeof column.key}`);
|
|
13316
|
+
}
|
|
13317
|
+
else {
|
|
13318
|
+
// Track key for uniqueness check
|
|
13319
|
+
if (!keyMap.has(column.key)) {
|
|
13320
|
+
keyMap.set(column.key, []);
|
|
13321
|
+
}
|
|
13322
|
+
keyMap.get(column.key).push(index);
|
|
13323
|
+
}
|
|
13324
|
+
if (typeof column.label !== "string" || column.label.trim() === "") {
|
|
13325
|
+
errors.push(`${configKey}[${index}].label must be a non-empty string, got ${typeof column.label === "string" ? `"${column.label}"` : typeof column.label}`);
|
|
13326
|
+
}
|
|
13327
|
+
// Validate format is a function
|
|
13328
|
+
if (typeof column.format !== "function") {
|
|
13329
|
+
errors.push(`${configKey}[${index}].format must be a function, got "${typeof column.format}"`);
|
|
13330
|
+
}
|
|
13331
|
+
// Validate isVisible is a function
|
|
13332
|
+
if (typeof column.isVisible !== "function") {
|
|
13333
|
+
errors.push(`${configKey}[${index}].isVisible must be a function, got "${typeof column.isVisible}"`);
|
|
13334
|
+
}
|
|
13335
|
+
});
|
|
13336
|
+
// Check for duplicate keys
|
|
13337
|
+
for (const [key, indexes] of keyMap.entries()) {
|
|
13338
|
+
if (indexes.length > 1) {
|
|
13339
|
+
errors.push(`${configKey}: Duplicate key "${key}" at indexes ${indexes.join(", ")}`);
|
|
13340
|
+
}
|
|
13341
|
+
}
|
|
13342
|
+
}
|
|
13343
|
+
// Throw aggregated errors if any
|
|
13344
|
+
if (errors.length > 0) {
|
|
13345
|
+
const errorMessage = `Column configuration validation failed:\n${errors
|
|
13346
|
+
.map((e, i) => ` ${i + 1}. ${e}`)
|
|
13347
|
+
.join("\n")}`;
|
|
13348
|
+
this.loggerService.warn(errorMessage);
|
|
13349
|
+
throw new Error(errorMessage);
|
|
13350
|
+
}
|
|
13351
|
+
this.loggerService.log("columnValidationService validation passed");
|
|
13352
|
+
};
|
|
13353
|
+
}
|
|
13354
|
+
}
|
|
13355
|
+
|
|
12797
13356
|
{
|
|
12798
13357
|
provide(TYPES.loggerService, () => new LoggerService());
|
|
12799
13358
|
}
|
|
@@ -12865,6 +13424,7 @@ class RiskMarkdownService {
|
|
|
12865
13424
|
provide(TYPES.riskValidationService, () => new RiskValidationService());
|
|
12866
13425
|
provide(TYPES.optimizerValidationService, () => new OptimizerValidationService());
|
|
12867
13426
|
provide(TYPES.configValidationService, () => new ConfigValidationService());
|
|
13427
|
+
provide(TYPES.columnValidationService, () => new ColumnValidationService());
|
|
12868
13428
|
}
|
|
12869
13429
|
{
|
|
12870
13430
|
provide(TYPES.optimizerTemplateService, () => new OptimizerTemplateService());
|
|
@@ -12941,6 +13501,7 @@ const validationServices = {
|
|
|
12941
13501
|
riskValidationService: inject(TYPES.riskValidationService),
|
|
12942
13502
|
optimizerValidationService: inject(TYPES.optimizerValidationService),
|
|
12943
13503
|
configValidationService: inject(TYPES.configValidationService),
|
|
13504
|
+
columnValidationService: inject(TYPES.columnValidationService),
|
|
12944
13505
|
};
|
|
12945
13506
|
const templateServices = {
|
|
12946
13507
|
optimizerTemplateService: inject(TYPES.optimizerTemplateService),
|
|
@@ -13040,6 +13601,77 @@ function getConfig() {
|
|
|
13040
13601
|
function getDefaultConfig() {
|
|
13041
13602
|
return DEFAULT_CONFIG;
|
|
13042
13603
|
}
|
|
13604
|
+
/**
|
|
13605
|
+
* Sets custom column configurations for markdown report generation.
|
|
13606
|
+
*
|
|
13607
|
+
* Allows overriding default column definitions for any report type.
|
|
13608
|
+
* All columns are validated before assignment to ensure structural correctness.
|
|
13609
|
+
*
|
|
13610
|
+
* @param columns - Partial column configuration object to override default column settings
|
|
13611
|
+
* @param _unsafe - Skip column validations - required for testbed
|
|
13612
|
+
*
|
|
13613
|
+
* @example
|
|
13614
|
+
* ```typescript
|
|
13615
|
+
* setColumns({
|
|
13616
|
+
* backtest_columns: [
|
|
13617
|
+
* {
|
|
13618
|
+
* key: "customId",
|
|
13619
|
+
* label: "Custom ID",
|
|
13620
|
+
* format: (data) => data.signal.id,
|
|
13621
|
+
* isVisible: () => true
|
|
13622
|
+
* }
|
|
13623
|
+
* ],
|
|
13624
|
+
* });
|
|
13625
|
+
* ```
|
|
13626
|
+
*
|
|
13627
|
+
* @throws {Error} If column configuration is invalid
|
|
13628
|
+
*/
|
|
13629
|
+
function setColumns(columns, _unsafe) {
|
|
13630
|
+
const prevConfig = Object.assign({}, COLUMN_CONFIG);
|
|
13631
|
+
try {
|
|
13632
|
+
Object.assign(COLUMN_CONFIG, columns);
|
|
13633
|
+
!_unsafe && backtest$1.columnValidationService.validate();
|
|
13634
|
+
}
|
|
13635
|
+
catch (error) {
|
|
13636
|
+
console.warn(`backtest-kit setColumns failed: ${functoolsKit.getErrorMessage(error)}`, columns);
|
|
13637
|
+
Object.assign(COLUMN_CONFIG, prevConfig);
|
|
13638
|
+
throw error;
|
|
13639
|
+
}
|
|
13640
|
+
}
|
|
13641
|
+
/**
|
|
13642
|
+
* Retrieves a copy of the current column configuration for markdown report generation.
|
|
13643
|
+
*
|
|
13644
|
+
* Returns a shallow copy of the current COLUMN_CONFIG to prevent accidental mutations.
|
|
13645
|
+
* Use this to inspect the current column definitions without modifying them.
|
|
13646
|
+
*
|
|
13647
|
+
* @returns {ColumnConfig} A copy of the current column configuration object
|
|
13648
|
+
*
|
|
13649
|
+
* @example
|
|
13650
|
+
* ```typescript
|
|
13651
|
+
* const currentColumns = getColumns();
|
|
13652
|
+
* console.log(currentColumns.backtest_columns.length);
|
|
13653
|
+
* ```
|
|
13654
|
+
*/
|
|
13655
|
+
function getColumns() {
|
|
13656
|
+
return Object.assign({}, COLUMN_CONFIG);
|
|
13657
|
+
}
|
|
13658
|
+
/**
|
|
13659
|
+
* Retrieves the default column configuration object for markdown report generation.
|
|
13660
|
+
*
|
|
13661
|
+
* Returns a reference to the default column definitions with all preset values.
|
|
13662
|
+
* Use this to see what column options are available and their default definitions.
|
|
13663
|
+
*
|
|
13664
|
+
* @returns {ColumnConfig} The default column configuration object
|
|
13665
|
+
*
|
|
13666
|
+
* @example
|
|
13667
|
+
* ```typescript
|
|
13668
|
+
* const defaultColumns = getDefaultColumns();
|
|
13669
|
+
* console.log(defaultColumns.backtest_columns);
|
|
13670
|
+
* ```
|
|
13671
|
+
*/
|
|
13672
|
+
function getDefaultColumns() {
|
|
13673
|
+
return DEFAULT_COLUMNS;
|
|
13674
|
+
}
|
|
13043
13675
|
|
|
13044
13676
|
const ADD_STRATEGY_METHOD_NAME = "add.addStrategy";
|
|
13045
13677
|
const ADD_EXCHANGE_METHOD_NAME = "add.addExchange";
|
|
@@ -15025,6 +15657,7 @@ class BacktestInstance {
|
|
|
15025
15657
|
*
|
|
15026
15658
|
* @param symbol - Trading pair symbol
|
|
15027
15659
|
* @param strategyName - Strategy name to generate report for
|
|
15660
|
+
* @param columns - Optional columns configuration for the report
|
|
15028
15661
|
* @returns Promise resolving to markdown formatted report string
|
|
15029
15662
|
*
|
|
15030
15663
|
* @example
|
|
@@ -15034,12 +15667,12 @@ class BacktestInstance {
|
|
|
15034
15667
|
* console.log(markdown);
|
|
15035
15668
|
* ```
|
|
15036
15669
|
*/
|
|
15037
|
-
this.getReport = async (symbol, strategyName) => {
|
|
15670
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15038
15671
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_GET_REPORT, {
|
|
15039
15672
|
symbol,
|
|
15040
15673
|
strategyName,
|
|
15041
15674
|
});
|
|
15042
|
-
return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName);
|
|
15675
|
+
return await backtest$1.backtestMarkdownService.getReport(symbol, strategyName, columns);
|
|
15043
15676
|
};
|
|
15044
15677
|
/**
|
|
15045
15678
|
* Saves strategy report to disk.
|
|
@@ -15047,6 +15680,7 @@ class BacktestInstance {
|
|
|
15047
15680
|
* @param symbol - Trading pair symbol
|
|
15048
15681
|
* @param strategyName - Strategy name to save report for
|
|
15049
15682
|
* @param path - Optional directory path to save report (default: "./dump/backtest")
|
|
15683
|
+
* @param columns - Optional columns configuration for the report
|
|
15050
15684
|
*
|
|
15051
15685
|
* @example
|
|
15052
15686
|
* ```typescript
|
|
@@ -15058,13 +15692,13 @@ class BacktestInstance {
|
|
|
15058
15692
|
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15059
15693
|
* ```
|
|
15060
15694
|
*/
|
|
15061
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
15695
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15062
15696
|
backtest$1.loggerService.info(BACKTEST_METHOD_NAME_DUMP, {
|
|
15063
15697
|
symbol,
|
|
15064
15698
|
strategyName,
|
|
15065
15699
|
path,
|
|
15066
15700
|
});
|
|
15067
|
-
await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path);
|
|
15701
|
+
await backtest$1.backtestMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15068
15702
|
};
|
|
15069
15703
|
}
|
|
15070
15704
|
}
|
|
@@ -15203,6 +15837,7 @@ class BacktestUtils {
|
|
|
15203
15837
|
*
|
|
15204
15838
|
* @param symbol - Trading pair symbol
|
|
15205
15839
|
* @param strategyName - Strategy name to generate report for
|
|
15840
|
+
* @param columns - Optional columns configuration for the report
|
|
15206
15841
|
* @returns Promise resolving to markdown formatted report string
|
|
15207
15842
|
*
|
|
15208
15843
|
* @example
|
|
@@ -15211,7 +15846,7 @@ class BacktestUtils {
|
|
|
15211
15846
|
* console.log(markdown);
|
|
15212
15847
|
* ```
|
|
15213
15848
|
*/
|
|
15214
|
-
this.getReport = async (symbol, strategyName) => {
|
|
15849
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15215
15850
|
backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
|
|
15216
15851
|
{
|
|
15217
15852
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15219,7 +15854,7 @@ class BacktestUtils {
|
|
|
15219
15854
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_REPORT));
|
|
15220
15855
|
}
|
|
15221
15856
|
const instance = this._getInstance(symbol, strategyName);
|
|
15222
|
-
return await instance.getReport(symbol, strategyName);
|
|
15857
|
+
return await instance.getReport(symbol, strategyName, columns);
|
|
15223
15858
|
};
|
|
15224
15859
|
/**
|
|
15225
15860
|
* Saves strategy report to disk.
|
|
@@ -15227,6 +15862,7 @@ class BacktestUtils {
|
|
|
15227
15862
|
* @param symbol - Trading pair symbol
|
|
15228
15863
|
* @param strategyName - Strategy name to save report for
|
|
15229
15864
|
* @param path - Optional directory path to save report (default: "./dump/backtest")
|
|
15865
|
+
* @param columns - Optional columns configuration for the report
|
|
15230
15866
|
*
|
|
15231
15867
|
* @example
|
|
15232
15868
|
* ```typescript
|
|
@@ -15237,7 +15873,7 @@ class BacktestUtils {
|
|
|
15237
15873
|
* await Backtest.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15238
15874
|
* ```
|
|
15239
15875
|
*/
|
|
15240
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
15876
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15241
15877
|
backtest$1.strategyValidationService.validate(strategyName, BACKTEST_METHOD_NAME_DUMP);
|
|
15242
15878
|
{
|
|
15243
15879
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15245,7 +15881,7 @@ class BacktestUtils {
|
|
|
15245
15881
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_DUMP));
|
|
15246
15882
|
}
|
|
15247
15883
|
const instance = this._getInstance(symbol, strategyName);
|
|
15248
|
-
return await instance.dump(symbol, strategyName, path);
|
|
15884
|
+
return await instance.dump(symbol, strategyName, path, columns);
|
|
15249
15885
|
};
|
|
15250
15886
|
/**
|
|
15251
15887
|
* Lists all active backtest instances with their current status.
|
|
@@ -15525,6 +16161,7 @@ class LiveInstance {
|
|
|
15525
16161
|
*
|
|
15526
16162
|
* @param symbol - Trading pair symbol
|
|
15527
16163
|
* @param strategyName - Strategy name to generate report for
|
|
16164
|
+
* @param columns - Optional columns configuration for the report
|
|
15528
16165
|
* @returns Promise resolving to markdown formatted report string
|
|
15529
16166
|
*
|
|
15530
16167
|
* @example
|
|
@@ -15534,12 +16171,12 @@ class LiveInstance {
|
|
|
15534
16171
|
* console.log(markdown);
|
|
15535
16172
|
* ```
|
|
15536
16173
|
*/
|
|
15537
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16174
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15538
16175
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_GET_REPORT, {
|
|
15539
16176
|
symbol,
|
|
15540
16177
|
strategyName,
|
|
15541
16178
|
});
|
|
15542
|
-
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName);
|
|
16179
|
+
return await backtest$1.liveMarkdownService.getReport(symbol, strategyName, columns);
|
|
15543
16180
|
};
|
|
15544
16181
|
/**
|
|
15545
16182
|
* Saves strategy report to disk.
|
|
@@ -15547,6 +16184,7 @@ class LiveInstance {
|
|
|
15547
16184
|
* @param symbol - Trading pair symbol
|
|
15548
16185
|
* @param strategyName - Strategy name to save report for
|
|
15549
16186
|
* @param path - Optional directory path to save report (default: "./dump/live")
|
|
16187
|
+
* @param columns - Optional columns configuration for the report
|
|
15550
16188
|
*
|
|
15551
16189
|
* @example
|
|
15552
16190
|
* ```typescript
|
|
@@ -15558,13 +16196,13 @@ class LiveInstance {
|
|
|
15558
16196
|
* await instance.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15559
16197
|
* ```
|
|
15560
16198
|
*/
|
|
15561
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16199
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15562
16200
|
backtest$1.loggerService.info(LIVE_METHOD_NAME_DUMP, {
|
|
15563
16201
|
symbol,
|
|
15564
16202
|
strategyName,
|
|
15565
16203
|
path,
|
|
15566
16204
|
});
|
|
15567
|
-
await backtest$1.liveMarkdownService.dump(symbol, strategyName, path);
|
|
16205
|
+
await backtest$1.liveMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15568
16206
|
};
|
|
15569
16207
|
}
|
|
15570
16208
|
}
|
|
@@ -15714,6 +16352,7 @@ class LiveUtils {
|
|
|
15714
16352
|
*
|
|
15715
16353
|
* @param symbol - Trading pair symbol
|
|
15716
16354
|
* @param strategyName - Strategy name to generate report for
|
|
16355
|
+
* @param columns - Optional columns configuration for the report
|
|
15717
16356
|
* @returns Promise resolving to markdown formatted report string
|
|
15718
16357
|
*
|
|
15719
16358
|
* @example
|
|
@@ -15722,7 +16361,7 @@ class LiveUtils {
|
|
|
15722
16361
|
* console.log(markdown);
|
|
15723
16362
|
* ```
|
|
15724
16363
|
*/
|
|
15725
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16364
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15726
16365
|
backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_GET_REPORT);
|
|
15727
16366
|
{
|
|
15728
16367
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15730,7 +16369,7 @@ class LiveUtils {
|
|
|
15730
16369
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT));
|
|
15731
16370
|
}
|
|
15732
16371
|
const instance = this._getInstance(symbol, strategyName);
|
|
15733
|
-
return await instance.getReport(symbol, strategyName);
|
|
16372
|
+
return await instance.getReport(symbol, strategyName, columns);
|
|
15734
16373
|
};
|
|
15735
16374
|
/**
|
|
15736
16375
|
* Saves strategy report to disk.
|
|
@@ -15738,6 +16377,7 @@ class LiveUtils {
|
|
|
15738
16377
|
* @param symbol - Trading pair symbol
|
|
15739
16378
|
* @param strategyName - Strategy name to save report for
|
|
15740
16379
|
* @param path - Optional directory path to save report (default: "./dump/live")
|
|
16380
|
+
* @param columns - Optional columns configuration for the report
|
|
15741
16381
|
*
|
|
15742
16382
|
* @example
|
|
15743
16383
|
* ```typescript
|
|
@@ -15748,7 +16388,7 @@ class LiveUtils {
|
|
|
15748
16388
|
* await Live.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15749
16389
|
* ```
|
|
15750
16390
|
*/
|
|
15751
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16391
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15752
16392
|
backtest$1.strategyValidationService.validate(strategyName, LIVE_METHOD_NAME_DUMP);
|
|
15753
16393
|
{
|
|
15754
16394
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
@@ -15756,7 +16396,7 @@ class LiveUtils {
|
|
|
15756
16396
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP));
|
|
15757
16397
|
}
|
|
15758
16398
|
const instance = this._getInstance(symbol, strategyName);
|
|
15759
|
-
return await instance.dump(symbol, strategyName, path);
|
|
16399
|
+
return await instance.dump(symbol, strategyName, path, columns);
|
|
15760
16400
|
};
|
|
15761
16401
|
/**
|
|
15762
16402
|
* Lists all active live trading instances with their current status.
|
|
@@ -15855,6 +16495,7 @@ class ScheduleUtils {
|
|
|
15855
16495
|
*
|
|
15856
16496
|
* @param symbol - Trading pair symbol
|
|
15857
16497
|
* @param strategyName - Strategy name to generate report for
|
|
16498
|
+
* @param columns - Optional columns configuration for the report
|
|
15858
16499
|
* @returns Promise resolving to markdown formatted report string
|
|
15859
16500
|
*
|
|
15860
16501
|
* @example
|
|
@@ -15863,7 +16504,7 @@ class ScheduleUtils {
|
|
|
15863
16504
|
* console.log(markdown);
|
|
15864
16505
|
* ```
|
|
15865
16506
|
*/
|
|
15866
|
-
this.getReport = async (symbol, strategyName) => {
|
|
16507
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
15867
16508
|
backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_GET_REPORT, {
|
|
15868
16509
|
symbol,
|
|
15869
16510
|
strategyName,
|
|
@@ -15874,7 +16515,7 @@ class ScheduleUtils {
|
|
|
15874
16515
|
riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
|
|
15875
16516
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT));
|
|
15876
16517
|
}
|
|
15877
|
-
return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName);
|
|
16518
|
+
return await backtest$1.scheduleMarkdownService.getReport(symbol, strategyName, columns);
|
|
15878
16519
|
};
|
|
15879
16520
|
/**
|
|
15880
16521
|
* Saves strategy report to disk.
|
|
@@ -15882,6 +16523,7 @@ class ScheduleUtils {
|
|
|
15882
16523
|
* @param symbol - Trading pair symbol
|
|
15883
16524
|
* @param strategyName - Strategy name to save report for
|
|
15884
16525
|
* @param path - Optional directory path to save report (default: "./dump/schedule")
|
|
16526
|
+
* @param columns - Optional columns configuration for the report
|
|
15885
16527
|
*
|
|
15886
16528
|
* @example
|
|
15887
16529
|
* ```typescript
|
|
@@ -15892,7 +16534,7 @@ class ScheduleUtils {
|
|
|
15892
16534
|
* await Schedule.dump("BTCUSDT", "my-strategy", "./custom/path");
|
|
15893
16535
|
* ```
|
|
15894
16536
|
*/
|
|
15895
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
16537
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
15896
16538
|
backtest$1.loggerService.info(SCHEDULE_METHOD_NAME_DUMP, {
|
|
15897
16539
|
symbol,
|
|
15898
16540
|
strategyName,
|
|
@@ -15904,7 +16546,7 @@ class ScheduleUtils {
|
|
|
15904
16546
|
riskName && backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
|
|
15905
16547
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP));
|
|
15906
16548
|
}
|
|
15907
|
-
await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path);
|
|
16549
|
+
await backtest$1.scheduleMarkdownService.dump(symbol, strategyName, path, columns);
|
|
15908
16550
|
};
|
|
15909
16551
|
}
|
|
15910
16552
|
}
|
|
@@ -16004,6 +16646,7 @@ class Performance {
|
|
|
16004
16646
|
*
|
|
16005
16647
|
* @param symbol - Trading pair symbol
|
|
16006
16648
|
* @param strategyName - Strategy name to generate report for
|
|
16649
|
+
* @param columns - Optional columns configuration for the report
|
|
16007
16650
|
* @returns Markdown formatted report string
|
|
16008
16651
|
*
|
|
16009
16652
|
* @example
|
|
@@ -16016,14 +16659,14 @@ class Performance {
|
|
|
16016
16659
|
* await fs.writeFile("performance-report.md", markdown);
|
|
16017
16660
|
* ```
|
|
16018
16661
|
*/
|
|
16019
|
-
static async getReport(symbol, strategyName) {
|
|
16662
|
+
static async getReport(symbol, strategyName, columns) {
|
|
16020
16663
|
backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
16021
16664
|
{
|
|
16022
16665
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16023
16666
|
riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
16024
16667
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT));
|
|
16025
16668
|
}
|
|
16026
|
-
return backtest$1.performanceMarkdownService.getReport(symbol, strategyName);
|
|
16669
|
+
return backtest$1.performanceMarkdownService.getReport(symbol, strategyName, columns);
|
|
16027
16670
|
}
|
|
16028
16671
|
/**
|
|
16029
16672
|
* Saves performance report to disk.
|
|
@@ -16034,6 +16677,7 @@ class Performance {
|
|
|
16034
16677
|
* @param symbol - Trading pair symbol
|
|
16035
16678
|
* @param strategyName - Strategy name to save report for
|
|
16036
16679
|
* @param path - Optional custom directory path
|
|
16680
|
+
* @param columns - Optional columns configuration for the report
|
|
16037
16681
|
*
|
|
16038
16682
|
* @example
|
|
16039
16683
|
* ```typescript
|
|
@@ -16044,14 +16688,14 @@ class Performance {
|
|
|
16044
16688
|
* await Performance.dump("BTCUSDT", "my-strategy", "./reports/perf");
|
|
16045
16689
|
* ```
|
|
16046
16690
|
*/
|
|
16047
|
-
static async dump(symbol, strategyName, path = "./dump/performance") {
|
|
16691
|
+
static async dump(symbol, strategyName, path = "./dump/performance", columns) {
|
|
16048
16692
|
backtest$1.strategyValidationService.validate(strategyName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
16049
16693
|
{
|
|
16050
16694
|
const { riskName, riskList } = backtest$1.strategySchemaService.get(strategyName);
|
|
16051
16695
|
riskName && backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
16052
16696
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP));
|
|
16053
16697
|
}
|
|
16054
|
-
return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path);
|
|
16698
|
+
return backtest$1.performanceMarkdownService.dump(symbol, strategyName, path, columns);
|
|
16055
16699
|
}
|
|
16056
16700
|
}
|
|
16057
16701
|
|
|
@@ -16313,6 +16957,8 @@ class WalkerInstance {
|
|
|
16313
16957
|
*
|
|
16314
16958
|
* @param symbol - Trading symbol
|
|
16315
16959
|
* @param walkerName - Walker name to generate report for
|
|
16960
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
16961
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16316
16962
|
* @returns Promise resolving to markdown formatted report string
|
|
16317
16963
|
*
|
|
16318
16964
|
* @example
|
|
@@ -16322,7 +16968,7 @@ class WalkerInstance {
|
|
|
16322
16968
|
* console.log(markdown);
|
|
16323
16969
|
* ```
|
|
16324
16970
|
*/
|
|
16325
|
-
this.getReport = async (symbol, walkerName) => {
|
|
16971
|
+
this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
|
|
16326
16972
|
backtest$1.loggerService.info(WALKER_METHOD_NAME_GET_REPORT, {
|
|
16327
16973
|
symbol,
|
|
16328
16974
|
walkerName,
|
|
@@ -16331,7 +16977,7 @@ class WalkerInstance {
|
|
|
16331
16977
|
return await backtest$1.walkerMarkdownService.getReport(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
|
|
16332
16978
|
exchangeName: walkerSchema.exchangeName,
|
|
16333
16979
|
frameName: walkerSchema.frameName,
|
|
16334
|
-
});
|
|
16980
|
+
}, strategyColumns, pnlColumns);
|
|
16335
16981
|
};
|
|
16336
16982
|
/**
|
|
16337
16983
|
* Saves walker report to disk.
|
|
@@ -16339,6 +16985,8 @@ class WalkerInstance {
|
|
|
16339
16985
|
* @param symbol - Trading symbol
|
|
16340
16986
|
* @param walkerName - Walker name to save report for
|
|
16341
16987
|
* @param path - Optional directory path to save report (default: "./dump/walker")
|
|
16988
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
16989
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16342
16990
|
*
|
|
16343
16991
|
* @example
|
|
16344
16992
|
* ```typescript
|
|
@@ -16350,7 +16998,7 @@ class WalkerInstance {
|
|
|
16350
16998
|
* await instance.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
16351
16999
|
* ```
|
|
16352
17000
|
*/
|
|
16353
|
-
this.dump = async (symbol, walkerName, path) => {
|
|
17001
|
+
this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
|
|
16354
17002
|
backtest$1.loggerService.info(WALKER_METHOD_NAME_DUMP, {
|
|
16355
17003
|
symbol,
|
|
16356
17004
|
walkerName,
|
|
@@ -16360,7 +17008,7 @@ class WalkerInstance {
|
|
|
16360
17008
|
await backtest$1.walkerMarkdownService.dump(walkerName, symbol, walkerSchema.metric || "sharpeRatio", {
|
|
16361
17009
|
exchangeName: walkerSchema.exchangeName,
|
|
16362
17010
|
frameName: walkerSchema.frameName,
|
|
16363
|
-
}, path);
|
|
17011
|
+
}, path, strategyColumns, pnlColumns);
|
|
16364
17012
|
};
|
|
16365
17013
|
}
|
|
16366
17014
|
}
|
|
@@ -16509,6 +17157,8 @@ class WalkerUtils {
|
|
|
16509
17157
|
*
|
|
16510
17158
|
* @param symbol - Trading symbol
|
|
16511
17159
|
* @param walkerName - Walker name to generate report for
|
|
17160
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17161
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16512
17162
|
* @returns Promise resolving to markdown formatted report string
|
|
16513
17163
|
*
|
|
16514
17164
|
* @example
|
|
@@ -16517,7 +17167,7 @@ class WalkerUtils {
|
|
|
16517
17167
|
* console.log(markdown);
|
|
16518
17168
|
* ```
|
|
16519
17169
|
*/
|
|
16520
|
-
this.getReport = async (symbol, walkerName) => {
|
|
17170
|
+
this.getReport = async (symbol, walkerName, strategyColumns, pnlColumns) => {
|
|
16521
17171
|
backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_GET_REPORT);
|
|
16522
17172
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16523
17173
|
for (const strategyName of walkerSchema.strategies) {
|
|
@@ -16527,7 +17177,7 @@ class WalkerUtils {
|
|
|
16527
17177
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_GET_REPORT));
|
|
16528
17178
|
}
|
|
16529
17179
|
const instance = this._getInstance(symbol, walkerName);
|
|
16530
|
-
return await instance.getReport(symbol, walkerName);
|
|
17180
|
+
return await instance.getReport(symbol, walkerName, strategyColumns, pnlColumns);
|
|
16531
17181
|
};
|
|
16532
17182
|
/**
|
|
16533
17183
|
* Saves walker report to disk.
|
|
@@ -16535,6 +17185,8 @@ class WalkerUtils {
|
|
|
16535
17185
|
* @param symbol - Trading symbol
|
|
16536
17186
|
* @param walkerName - Walker name to save report for
|
|
16537
17187
|
* @param path - Optional directory path to save report (default: "./dump/walker")
|
|
17188
|
+
* @param strategyColumns - Optional strategy columns configuration
|
|
17189
|
+
* @param pnlColumns - Optional PNL columns configuration
|
|
16538
17190
|
*
|
|
16539
17191
|
* @example
|
|
16540
17192
|
* ```typescript
|
|
@@ -16545,7 +17197,7 @@ class WalkerUtils {
|
|
|
16545
17197
|
* await Walker.dump("BTCUSDT", "my-walker", "./custom/path");
|
|
16546
17198
|
* ```
|
|
16547
17199
|
*/
|
|
16548
|
-
this.dump = async (symbol, walkerName, path) => {
|
|
17200
|
+
this.dump = async (symbol, walkerName, path, strategyColumns, pnlColumns) => {
|
|
16549
17201
|
backtest$1.walkerValidationService.validate(walkerName, WALKER_METHOD_NAME_DUMP);
|
|
16550
17202
|
const walkerSchema = backtest$1.walkerSchemaService.get(walkerName);
|
|
16551
17203
|
for (const strategyName of walkerSchema.strategies) {
|
|
@@ -16555,7 +17207,7 @@ class WalkerUtils {
|
|
|
16555
17207
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, WALKER_METHOD_NAME_DUMP));
|
|
16556
17208
|
}
|
|
16557
17209
|
const instance = this._getInstance(symbol, walkerName);
|
|
16558
|
-
return await instance.dump(symbol, walkerName, path);
|
|
17210
|
+
return await instance.dump(symbol, walkerName, path, strategyColumns, pnlColumns);
|
|
16559
17211
|
};
|
|
16560
17212
|
/**
|
|
16561
17213
|
* Lists all active walker instances with their current status.
|
|
@@ -16661,6 +17313,7 @@ class HeatUtils {
|
|
|
16661
17313
|
* Symbols are sorted by Total PNL descending.
|
|
16662
17314
|
*
|
|
16663
17315
|
* @param strategyName - Strategy name to generate heatmap report for
|
|
17316
|
+
* @param columns - Optional columns configuration for the report
|
|
16664
17317
|
* @returns Promise resolving to markdown formatted report string
|
|
16665
17318
|
*
|
|
16666
17319
|
* @example
|
|
@@ -16679,7 +17332,7 @@ class HeatUtils {
|
|
|
16679
17332
|
* // ...
|
|
16680
17333
|
* ```
|
|
16681
17334
|
*/
|
|
16682
|
-
this.getReport = async (strategyName) => {
|
|
17335
|
+
this.getReport = async (strategyName, columns) => {
|
|
16683
17336
|
backtest$1.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName });
|
|
16684
17337
|
backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_GET_REPORT);
|
|
16685
17338
|
{
|
|
@@ -16687,7 +17340,7 @@ class HeatUtils {
|
|
|
16687
17340
|
riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
|
|
16688
17341
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT));
|
|
16689
17342
|
}
|
|
16690
|
-
return await backtest$1.heatMarkdownService.getReport(strategyName);
|
|
17343
|
+
return await backtest$1.heatMarkdownService.getReport(strategyName, columns);
|
|
16691
17344
|
};
|
|
16692
17345
|
/**
|
|
16693
17346
|
* Saves heatmap report to disk for a strategy.
|
|
@@ -16697,6 +17350,7 @@ class HeatUtils {
|
|
|
16697
17350
|
*
|
|
16698
17351
|
* @param strategyName - Strategy name to save heatmap report for
|
|
16699
17352
|
* @param path - Optional directory path to save report (default: "./dump/heatmap")
|
|
17353
|
+
* @param columns - Optional columns configuration for the report
|
|
16700
17354
|
*
|
|
16701
17355
|
* @example
|
|
16702
17356
|
* ```typescript
|
|
@@ -16707,7 +17361,7 @@ class HeatUtils {
|
|
|
16707
17361
|
* await Heat.dump("my-strategy", "./reports");
|
|
16708
17362
|
* ```
|
|
16709
17363
|
*/
|
|
16710
|
-
this.dump = async (strategyName, path) => {
|
|
17364
|
+
this.dump = async (strategyName, path, columns) => {
|
|
16711
17365
|
backtest$1.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName, path });
|
|
16712
17366
|
backtest$1.strategyValidationService.validate(strategyName, HEAT_METHOD_NAME_DUMP);
|
|
16713
17367
|
{
|
|
@@ -16715,7 +17369,7 @@ class HeatUtils {
|
|
|
16715
17369
|
riskName && backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
|
|
16716
17370
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP));
|
|
16717
17371
|
}
|
|
16718
|
-
await backtest$1.heatMarkdownService.dump(strategyName, path);
|
|
17372
|
+
await backtest$1.heatMarkdownService.dump(strategyName, path, columns);
|
|
16719
17373
|
};
|
|
16720
17374
|
}
|
|
16721
17375
|
}
|
|
@@ -17015,7 +17669,7 @@ class PartialUtils {
|
|
|
17015
17669
|
*
|
|
17016
17670
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17017
17671
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17018
|
-
* @returns Promise resolving to
|
|
17672
|
+
* @returns Promise resolving to PartialStatisticsModel object with counts and event list
|
|
17019
17673
|
*
|
|
17020
17674
|
* @example
|
|
17021
17675
|
* ```typescript
|
|
@@ -17059,6 +17713,7 @@ class PartialUtils {
|
|
|
17059
17713
|
*
|
|
17060
17714
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17061
17715
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17716
|
+
* @param columns - Optional columns configuration for the report
|
|
17062
17717
|
* @returns Promise resolving to markdown formatted report string
|
|
17063
17718
|
*
|
|
17064
17719
|
* @example
|
|
@@ -17079,7 +17734,7 @@ class PartialUtils {
|
|
|
17079
17734
|
* // **Loss events:** 1
|
|
17080
17735
|
* ```
|
|
17081
17736
|
*/
|
|
17082
|
-
this.getReport = async (symbol, strategyName) => {
|
|
17737
|
+
this.getReport = async (symbol, strategyName, columns) => {
|
|
17083
17738
|
backtest$1.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName });
|
|
17084
17739
|
backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
17085
17740
|
{
|
|
@@ -17087,7 +17742,7 @@ class PartialUtils {
|
|
|
17087
17742
|
riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
17088
17743
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT));
|
|
17089
17744
|
}
|
|
17090
|
-
return await backtest$1.partialMarkdownService.getReport(symbol, strategyName);
|
|
17745
|
+
return await backtest$1.partialMarkdownService.getReport(symbol, strategyName, columns);
|
|
17091
17746
|
};
|
|
17092
17747
|
/**
|
|
17093
17748
|
* Generates and saves markdown report to file.
|
|
@@ -17104,6 +17759,7 @@ class PartialUtils {
|
|
|
17104
17759
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
17105
17760
|
* @param strategyName - Strategy name (e.g., "my-strategy")
|
|
17106
17761
|
* @param path - Output directory path (default: "./dump/partial")
|
|
17762
|
+
* @param columns - Optional columns configuration for the report
|
|
17107
17763
|
* @returns Promise that resolves when file is written
|
|
17108
17764
|
*
|
|
17109
17765
|
* @example
|
|
@@ -17120,7 +17776,7 @@ class PartialUtils {
|
|
|
17120
17776
|
* }
|
|
17121
17777
|
* ```
|
|
17122
17778
|
*/
|
|
17123
|
-
this.dump = async (symbol, strategyName, path) => {
|
|
17779
|
+
this.dump = async (symbol, strategyName, path, columns) => {
|
|
17124
17780
|
backtest$1.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName, path });
|
|
17125
17781
|
backtest$1.strategyValidationService.validate(strategyName, PARTIAL_METHOD_NAME_DUMP);
|
|
17126
17782
|
{
|
|
@@ -17128,7 +17784,7 @@ class PartialUtils {
|
|
|
17128
17784
|
riskName && backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
|
|
17129
17785
|
riskList && riskList.forEach((riskName) => backtest$1.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP));
|
|
17130
17786
|
}
|
|
17131
|
-
await backtest$1.partialMarkdownService.dump(symbol, strategyName, path);
|
|
17787
|
+
await backtest$1.partialMarkdownService.dump(symbol, strategyName, path, columns);
|
|
17132
17788
|
};
|
|
17133
17789
|
}
|
|
17134
17790
|
}
|
|
@@ -17260,8 +17916,10 @@ exports.formatPrice = formatPrice;
|
|
|
17260
17916
|
exports.formatQuantity = formatQuantity;
|
|
17261
17917
|
exports.getAveragePrice = getAveragePrice;
|
|
17262
17918
|
exports.getCandles = getCandles;
|
|
17919
|
+
exports.getColumns = getColumns;
|
|
17263
17920
|
exports.getConfig = getConfig;
|
|
17264
17921
|
exports.getDate = getDate;
|
|
17922
|
+
exports.getDefaultColumns = getDefaultColumns;
|
|
17265
17923
|
exports.getDefaultConfig = getDefaultConfig;
|
|
17266
17924
|
exports.getMode = getMode;
|
|
17267
17925
|
exports.lib = backtest;
|
|
@@ -17300,5 +17958,6 @@ exports.listenWalker = listenWalker;
|
|
|
17300
17958
|
exports.listenWalkerComplete = listenWalkerComplete;
|
|
17301
17959
|
exports.listenWalkerOnce = listenWalkerOnce;
|
|
17302
17960
|
exports.listenWalkerProgress = listenWalkerProgress;
|
|
17961
|
+
exports.setColumns = setColumns;
|
|
17303
17962
|
exports.setConfig = setConfig;
|
|
17304
17963
|
exports.setLogger = setLogger;
|