backtest-kit 1.5.14 → 1.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.cjs +101 -53
- package/build/index.mjs +101 -53
- package/package.json +1 -1
- package/types.d.ts +33 -10
package/build/index.cjs
CHANGED
|
@@ -590,6 +590,14 @@ class ClientExchange {
|
|
|
590
590
|
const vwap = sumPriceVolume / totalVolume;
|
|
591
591
|
return vwap;
|
|
592
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Formats quantity according to exchange-specific rules for the given symbol.
|
|
595
|
+
* Applies proper decimal precision and rounding based on symbol's lot size filters.
|
|
596
|
+
*
|
|
597
|
+
* @param symbol - Trading pair symbol
|
|
598
|
+
* @param quantity - Raw quantity to format
|
|
599
|
+
* @returns Promise resolving to formatted quantity as string
|
|
600
|
+
*/
|
|
593
601
|
async formatQuantity(symbol, quantity) {
|
|
594
602
|
this.params.logger.debug("binanceService formatQuantity", {
|
|
595
603
|
symbol,
|
|
@@ -597,6 +605,14 @@ class ClientExchange {
|
|
|
597
605
|
});
|
|
598
606
|
return await this.params.formatQuantity(symbol, quantity);
|
|
599
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* Formats price according to exchange-specific rules for the given symbol.
|
|
610
|
+
* Applies proper decimal precision and rounding based on symbol's price filters.
|
|
611
|
+
*
|
|
612
|
+
* @param symbol - Trading pair symbol
|
|
613
|
+
* @param price - Raw price to format
|
|
614
|
+
* @returns Promise resolving to formatted price as string
|
|
615
|
+
*/
|
|
600
616
|
async formatPrice(symbol, price) {
|
|
601
617
|
this.params.logger.debug("binanceService formatPrice", {
|
|
602
618
|
symbol,
|
|
@@ -6471,7 +6487,7 @@ const columns$3 = [
|
|
|
6471
6487
|
},
|
|
6472
6488
|
];
|
|
6473
6489
|
/** Maximum number of events to store in live trading reports */
|
|
6474
|
-
const MAX_EVENTS$
|
|
6490
|
+
const MAX_EVENTS$4 = 250;
|
|
6475
6491
|
/**
|
|
6476
6492
|
* Storage class for accumulating all tick events per strategy.
|
|
6477
6493
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
@@ -6504,7 +6520,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6504
6520
|
}
|
|
6505
6521
|
{
|
|
6506
6522
|
this._eventList.push(newEvent);
|
|
6507
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
6523
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6508
6524
|
this._eventList.shift();
|
|
6509
6525
|
}
|
|
6510
6526
|
}
|
|
@@ -6528,19 +6544,16 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6528
6544
|
stopLoss: data.signal.priceStopLoss,
|
|
6529
6545
|
});
|
|
6530
6546
|
// Trim queue if exceeded MAX_EVENTS
|
|
6531
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
6547
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6532
6548
|
this._eventList.shift();
|
|
6533
6549
|
}
|
|
6534
6550
|
}
|
|
6535
6551
|
/**
|
|
6536
|
-
*
|
|
6537
|
-
* Replaces the previous event with the same signalId.
|
|
6552
|
+
* Adds an active event to the storage.
|
|
6538
6553
|
*
|
|
6539
6554
|
* @param data - Active tick result
|
|
6540
6555
|
*/
|
|
6541
6556
|
addActiveEvent(data) {
|
|
6542
|
-
// Find existing event with the same signalId
|
|
6543
|
-
const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
|
|
6544
6557
|
const newEvent = {
|
|
6545
6558
|
timestamp: Date.now(),
|
|
6546
6559
|
action: "active",
|
|
@@ -6555,29 +6568,20 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6555
6568
|
percentTp: data.percentTp,
|
|
6556
6569
|
percentSl: data.percentSl,
|
|
6557
6570
|
};
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
else {
|
|
6563
|
-
this._eventList.push(newEvent);
|
|
6564
|
-
// Trim queue if exceeded MAX_EVENTS
|
|
6565
|
-
if (this._eventList.length > MAX_EVENTS$3) {
|
|
6566
|
-
this._eventList.shift();
|
|
6567
|
-
}
|
|
6571
|
+
this._eventList.push(newEvent);
|
|
6572
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
6573
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6574
|
+
this._eventList.shift();
|
|
6568
6575
|
}
|
|
6569
6576
|
}
|
|
6570
6577
|
/**
|
|
6571
|
-
*
|
|
6572
|
-
* Replaces the previous event with the same signalId.
|
|
6578
|
+
* Adds a closed event to the storage.
|
|
6573
6579
|
*
|
|
6574
6580
|
* @param data - Closed tick result
|
|
6575
6581
|
*/
|
|
6576
6582
|
addClosedEvent(data) {
|
|
6577
6583
|
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
6578
6584
|
const durationMin = Math.round(durationMs / 60000);
|
|
6579
|
-
// Find existing event with the same signalId
|
|
6580
|
-
const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
|
|
6581
6585
|
const newEvent = {
|
|
6582
6586
|
timestamp: data.closeTimestamp,
|
|
6583
6587
|
action: "closed",
|
|
@@ -6593,16 +6597,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6593
6597
|
closeReason: data.closeReason,
|
|
6594
6598
|
duration: durationMin,
|
|
6595
6599
|
};
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
else {
|
|
6601
|
-
this._eventList.push(newEvent);
|
|
6602
|
-
// Trim queue if exceeded MAX_EVENTS
|
|
6603
|
-
if (this._eventList.length > MAX_EVENTS$3) {
|
|
6604
|
-
this._eventList.shift();
|
|
6605
|
-
}
|
|
6600
|
+
this._eventList.push(newEvent);
|
|
6601
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
6602
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6603
|
+
this._eventList.shift();
|
|
6606
6604
|
}
|
|
6607
6605
|
}
|
|
6608
6606
|
/**
|
|
@@ -7003,7 +7001,7 @@ const columns$2 = [
|
|
|
7003
7001
|
},
|
|
7004
7002
|
];
|
|
7005
7003
|
/** Maximum number of events to store in schedule reports */
|
|
7006
|
-
const MAX_EVENTS$
|
|
7004
|
+
const MAX_EVENTS$3 = 250;
|
|
7007
7005
|
/**
|
|
7008
7006
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
7009
7007
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
@@ -7032,21 +7030,45 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7032
7030
|
stopLoss: data.signal.priceStopLoss,
|
|
7033
7031
|
});
|
|
7034
7032
|
// Trim queue if exceeded MAX_EVENTS
|
|
7035
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
7033
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
7034
|
+
this._eventList.shift();
|
|
7035
|
+
}
|
|
7036
|
+
}
|
|
7037
|
+
/**
|
|
7038
|
+
* Adds an opened event to the storage.
|
|
7039
|
+
*
|
|
7040
|
+
* @param data - Opened tick result
|
|
7041
|
+
*/
|
|
7042
|
+
addOpenedEvent(data) {
|
|
7043
|
+
const durationMs = data.signal.pendingAt - data.signal.scheduledAt;
|
|
7044
|
+
const durationMin = Math.round(durationMs / 60000);
|
|
7045
|
+
const newEvent = {
|
|
7046
|
+
timestamp: data.signal.pendingAt,
|
|
7047
|
+
action: "opened",
|
|
7048
|
+
symbol: data.signal.symbol,
|
|
7049
|
+
signalId: data.signal.id,
|
|
7050
|
+
position: data.signal.position,
|
|
7051
|
+
note: data.signal.note,
|
|
7052
|
+
currentPrice: data.currentPrice,
|
|
7053
|
+
priceOpen: data.signal.priceOpen,
|
|
7054
|
+
takeProfit: data.signal.priceTakeProfit,
|
|
7055
|
+
stopLoss: data.signal.priceStopLoss,
|
|
7056
|
+
duration: durationMin,
|
|
7057
|
+
};
|
|
7058
|
+
this._eventList.push(newEvent);
|
|
7059
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
7060
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
7036
7061
|
this._eventList.shift();
|
|
7037
7062
|
}
|
|
7038
7063
|
}
|
|
7039
7064
|
/**
|
|
7040
|
-
*
|
|
7041
|
-
* Replaces the previous event with the same signalId.
|
|
7065
|
+
* Adds a cancelled event to the storage.
|
|
7042
7066
|
*
|
|
7043
7067
|
* @param data - Cancelled tick result
|
|
7044
7068
|
*/
|
|
7045
7069
|
addCancelledEvent(data) {
|
|
7046
7070
|
const durationMs = data.closeTimestamp - data.signal.scheduledAt;
|
|
7047
7071
|
const durationMin = Math.round(durationMs / 60000);
|
|
7048
|
-
// Find existing event with the same signalId
|
|
7049
|
-
const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
|
|
7050
7072
|
const newEvent = {
|
|
7051
7073
|
timestamp: data.closeTimestamp,
|
|
7052
7074
|
action: "cancelled",
|
|
@@ -7061,16 +7083,10 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7061
7083
|
closeTimestamp: data.closeTimestamp,
|
|
7062
7084
|
duration: durationMin,
|
|
7063
7085
|
};
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
else {
|
|
7069
|
-
this._eventList.push(newEvent);
|
|
7070
|
-
// Trim queue if exceeded MAX_EVENTS
|
|
7071
|
-
if (this._eventList.length > MAX_EVENTS$2) {
|
|
7072
|
-
this._eventList.shift();
|
|
7073
|
-
}
|
|
7086
|
+
this._eventList.push(newEvent);
|
|
7087
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
7088
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
7089
|
+
this._eventList.shift();
|
|
7074
7090
|
}
|
|
7075
7091
|
}
|
|
7076
7092
|
/**
|
|
@@ -7084,29 +7100,44 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7084
7100
|
eventList: [],
|
|
7085
7101
|
totalEvents: 0,
|
|
7086
7102
|
totalScheduled: 0,
|
|
7103
|
+
totalOpened: 0,
|
|
7087
7104
|
totalCancelled: 0,
|
|
7088
7105
|
cancellationRate: null,
|
|
7106
|
+
activationRate: null,
|
|
7089
7107
|
avgWaitTime: null,
|
|
7108
|
+
avgActivationTime: null,
|
|
7090
7109
|
};
|
|
7091
7110
|
}
|
|
7092
7111
|
const scheduledEvents = this._eventList.filter((e) => e.action === "scheduled");
|
|
7112
|
+
const openedEvents = this._eventList.filter((e) => e.action === "opened");
|
|
7093
7113
|
const cancelledEvents = this._eventList.filter((e) => e.action === "cancelled");
|
|
7094
7114
|
const totalScheduled = scheduledEvents.length;
|
|
7115
|
+
const totalOpened = openedEvents.length;
|
|
7095
7116
|
const totalCancelled = cancelledEvents.length;
|
|
7096
7117
|
// Calculate cancellation rate
|
|
7097
7118
|
const cancellationRate = totalScheduled > 0 ? (totalCancelled / totalScheduled) * 100 : null;
|
|
7119
|
+
// Calculate activation rate
|
|
7120
|
+
const activationRate = totalScheduled > 0 ? (totalOpened / totalScheduled) * 100 : null;
|
|
7098
7121
|
// Calculate average wait time for cancelled signals
|
|
7099
7122
|
const avgWaitTime = totalCancelled > 0
|
|
7100
7123
|
? cancelledEvents.reduce((sum, e) => sum + (e.duration || 0), 0) /
|
|
7101
7124
|
totalCancelled
|
|
7102
7125
|
: null;
|
|
7126
|
+
// Calculate average activation time for opened signals
|
|
7127
|
+
const avgActivationTime = totalOpened > 0
|
|
7128
|
+
? openedEvents.reduce((sum, e) => sum + (e.duration || 0), 0) /
|
|
7129
|
+
totalOpened
|
|
7130
|
+
: null;
|
|
7103
7131
|
return {
|
|
7104
7132
|
eventList: this._eventList,
|
|
7105
7133
|
totalEvents: this._eventList.length,
|
|
7106
7134
|
totalScheduled,
|
|
7135
|
+
totalOpened,
|
|
7107
7136
|
totalCancelled,
|
|
7108
7137
|
cancellationRate,
|
|
7138
|
+
activationRate,
|
|
7109
7139
|
avgWaitTime,
|
|
7140
|
+
avgActivationTime,
|
|
7110
7141
|
};
|
|
7111
7142
|
}
|
|
7112
7143
|
/**
|
|
@@ -7136,8 +7167,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7136
7167
|
"",
|
|
7137
7168
|
`**Total events:** ${stats.totalEvents}`,
|
|
7138
7169
|
`**Scheduled signals:** ${stats.totalScheduled}`,
|
|
7170
|
+
`**Opened signals:** ${stats.totalOpened}`,
|
|
7139
7171
|
`**Cancelled signals:** ${stats.totalCancelled}`,
|
|
7172
|
+
`**Activation rate:** ${stats.activationRate === null ? "N/A" : `${stats.activationRate.toFixed(2)}% (higher is better)`}`,
|
|
7140
7173
|
`**Cancellation rate:** ${stats.cancellationRate === null ? "N/A" : `${stats.cancellationRate.toFixed(2)}% (lower is better)`}`,
|
|
7174
|
+
`**Average activation time:** ${stats.avgActivationTime === null ? "N/A" : `${stats.avgActivationTime.toFixed(2)} minutes`}`,
|
|
7141
7175
|
`**Average wait time (cancelled):** ${stats.avgWaitTime === null ? "N/A" : `${stats.avgWaitTime.toFixed(2)} minutes`}`
|
|
7142
7176
|
].join("\n");
|
|
7143
7177
|
}
|
|
@@ -7193,10 +7227,10 @@ class ScheduleMarkdownService {
|
|
|
7193
7227
|
*/
|
|
7194
7228
|
this.getStorage = functoolsKit.memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$2());
|
|
7195
7229
|
/**
|
|
7196
|
-
* Processes tick events and accumulates scheduled/cancelled events.
|
|
7197
|
-
* Should be called from
|
|
7230
|
+
* Processes tick events and accumulates scheduled/opened/cancelled events.
|
|
7231
|
+
* Should be called from signalEmitter subscription.
|
|
7198
7232
|
*
|
|
7199
|
-
* Processes only scheduled and cancelled event types.
|
|
7233
|
+
* Processes only scheduled, opened and cancelled event types.
|
|
7200
7234
|
*
|
|
7201
7235
|
* @param data - Tick result from strategy execution
|
|
7202
7236
|
*
|
|
@@ -7214,6 +7248,13 @@ class ScheduleMarkdownService {
|
|
|
7214
7248
|
if (data.action === "scheduled") {
|
|
7215
7249
|
storage.addScheduledEvent(data);
|
|
7216
7250
|
}
|
|
7251
|
+
else if (data.action === "opened") {
|
|
7252
|
+
// Check if this opened signal was previously scheduled
|
|
7253
|
+
// by checking if signal has scheduledAt != pendingAt
|
|
7254
|
+
if (data.signal.scheduledAt !== data.signal.pendingAt) {
|
|
7255
|
+
storage.addOpenedEvent(data);
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7217
7258
|
else if (data.action === "cancelled") {
|
|
7218
7259
|
storage.addCancelledEvent(data);
|
|
7219
7260
|
}
|
|
@@ -7351,7 +7392,7 @@ function percentile(sortedArray, p) {
|
|
|
7351
7392
|
return sortedArray[Math.max(0, index)];
|
|
7352
7393
|
}
|
|
7353
7394
|
/** Maximum number of performance events to store per strategy */
|
|
7354
|
-
const MAX_EVENTS$
|
|
7395
|
+
const MAX_EVENTS$2 = 10000;
|
|
7355
7396
|
/**
|
|
7356
7397
|
* Storage class for accumulating performance metrics per strategy.
|
|
7357
7398
|
* Maintains a list of all performance events and provides aggregated statistics.
|
|
@@ -7369,7 +7410,7 @@ class PerformanceStorage {
|
|
|
7369
7410
|
addEvent(event) {
|
|
7370
7411
|
this._events.push(event);
|
|
7371
7412
|
// Trim queue if exceeded MAX_EVENTS (keep most recent)
|
|
7372
|
-
if (this._events.length > MAX_EVENTS$
|
|
7413
|
+
if (this._events.length > MAX_EVENTS$2) {
|
|
7373
7414
|
this._events.shift();
|
|
7374
7415
|
}
|
|
7375
7416
|
}
|
|
@@ -8281,6 +8322,8 @@ const columns$1 = [
|
|
|
8281
8322
|
format: (data) => data.totalTrades.toString(),
|
|
8282
8323
|
},
|
|
8283
8324
|
];
|
|
8325
|
+
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
8326
|
+
const MAX_EVENTS$1 = 250;
|
|
8284
8327
|
/**
|
|
8285
8328
|
* Storage class for accumulating closed signals per strategy and generating heatmap.
|
|
8286
8329
|
* Maintains symbol-level statistics and provides portfolio-wide metrics.
|
|
@@ -8300,7 +8343,12 @@ class HeatmapStorage {
|
|
|
8300
8343
|
if (!this.symbolData.has(symbol)) {
|
|
8301
8344
|
this.symbolData.set(symbol, []);
|
|
8302
8345
|
}
|
|
8303
|
-
this.symbolData.get(symbol)
|
|
8346
|
+
const signals = this.symbolData.get(symbol);
|
|
8347
|
+
signals.push(data);
|
|
8348
|
+
// Trim queue if exceeded MAX_EVENTS per symbol
|
|
8349
|
+
if (signals.length > MAX_EVENTS$1) {
|
|
8350
|
+
signals.shift();
|
|
8351
|
+
}
|
|
8304
8352
|
}
|
|
8305
8353
|
/**
|
|
8306
8354
|
* Calculates statistics for a single symbol.
|
package/build/index.mjs
CHANGED
|
@@ -588,6 +588,14 @@ class ClientExchange {
|
|
|
588
588
|
const vwap = sumPriceVolume / totalVolume;
|
|
589
589
|
return vwap;
|
|
590
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Formats quantity according to exchange-specific rules for the given symbol.
|
|
593
|
+
* Applies proper decimal precision and rounding based on symbol's lot size filters.
|
|
594
|
+
*
|
|
595
|
+
* @param symbol - Trading pair symbol
|
|
596
|
+
* @param quantity - Raw quantity to format
|
|
597
|
+
* @returns Promise resolving to formatted quantity as string
|
|
598
|
+
*/
|
|
591
599
|
async formatQuantity(symbol, quantity) {
|
|
592
600
|
this.params.logger.debug("binanceService formatQuantity", {
|
|
593
601
|
symbol,
|
|
@@ -595,6 +603,14 @@ class ClientExchange {
|
|
|
595
603
|
});
|
|
596
604
|
return await this.params.formatQuantity(symbol, quantity);
|
|
597
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Formats price according to exchange-specific rules for the given symbol.
|
|
608
|
+
* Applies proper decimal precision and rounding based on symbol's price filters.
|
|
609
|
+
*
|
|
610
|
+
* @param symbol - Trading pair symbol
|
|
611
|
+
* @param price - Raw price to format
|
|
612
|
+
* @returns Promise resolving to formatted price as string
|
|
613
|
+
*/
|
|
598
614
|
async formatPrice(symbol, price) {
|
|
599
615
|
this.params.logger.debug("binanceService formatPrice", {
|
|
600
616
|
symbol,
|
|
@@ -6469,7 +6485,7 @@ const columns$3 = [
|
|
|
6469
6485
|
},
|
|
6470
6486
|
];
|
|
6471
6487
|
/** Maximum number of events to store in live trading reports */
|
|
6472
|
-
const MAX_EVENTS$
|
|
6488
|
+
const MAX_EVENTS$4 = 250;
|
|
6473
6489
|
/**
|
|
6474
6490
|
* Storage class for accumulating all tick events per strategy.
|
|
6475
6491
|
* Maintains a chronological list of all events (idle, opened, active, closed).
|
|
@@ -6502,7 +6518,7 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6502
6518
|
}
|
|
6503
6519
|
{
|
|
6504
6520
|
this._eventList.push(newEvent);
|
|
6505
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
6521
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6506
6522
|
this._eventList.shift();
|
|
6507
6523
|
}
|
|
6508
6524
|
}
|
|
@@ -6526,19 +6542,16 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6526
6542
|
stopLoss: data.signal.priceStopLoss,
|
|
6527
6543
|
});
|
|
6528
6544
|
// Trim queue if exceeded MAX_EVENTS
|
|
6529
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
6545
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6530
6546
|
this._eventList.shift();
|
|
6531
6547
|
}
|
|
6532
6548
|
}
|
|
6533
6549
|
/**
|
|
6534
|
-
*
|
|
6535
|
-
* Replaces the previous event with the same signalId.
|
|
6550
|
+
* Adds an active event to the storage.
|
|
6536
6551
|
*
|
|
6537
6552
|
* @param data - Active tick result
|
|
6538
6553
|
*/
|
|
6539
6554
|
addActiveEvent(data) {
|
|
6540
|
-
// Find existing event with the same signalId
|
|
6541
|
-
const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
|
|
6542
6555
|
const newEvent = {
|
|
6543
6556
|
timestamp: Date.now(),
|
|
6544
6557
|
action: "active",
|
|
@@ -6553,29 +6566,20 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6553
6566
|
percentTp: data.percentTp,
|
|
6554
6567
|
percentSl: data.percentSl,
|
|
6555
6568
|
};
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
else {
|
|
6561
|
-
this._eventList.push(newEvent);
|
|
6562
|
-
// Trim queue if exceeded MAX_EVENTS
|
|
6563
|
-
if (this._eventList.length > MAX_EVENTS$3) {
|
|
6564
|
-
this._eventList.shift();
|
|
6565
|
-
}
|
|
6569
|
+
this._eventList.push(newEvent);
|
|
6570
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
6571
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6572
|
+
this._eventList.shift();
|
|
6566
6573
|
}
|
|
6567
6574
|
}
|
|
6568
6575
|
/**
|
|
6569
|
-
*
|
|
6570
|
-
* Replaces the previous event with the same signalId.
|
|
6576
|
+
* Adds a closed event to the storage.
|
|
6571
6577
|
*
|
|
6572
6578
|
* @param data - Closed tick result
|
|
6573
6579
|
*/
|
|
6574
6580
|
addClosedEvent(data) {
|
|
6575
6581
|
const durationMs = data.closeTimestamp - data.signal.pendingAt;
|
|
6576
6582
|
const durationMin = Math.round(durationMs / 60000);
|
|
6577
|
-
// Find existing event with the same signalId
|
|
6578
|
-
const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
|
|
6579
6583
|
const newEvent = {
|
|
6580
6584
|
timestamp: data.closeTimestamp,
|
|
6581
6585
|
action: "closed",
|
|
@@ -6591,16 +6595,10 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6591
6595
|
closeReason: data.closeReason,
|
|
6592
6596
|
duration: durationMin,
|
|
6593
6597
|
};
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
else {
|
|
6599
|
-
this._eventList.push(newEvent);
|
|
6600
|
-
// Trim queue if exceeded MAX_EVENTS
|
|
6601
|
-
if (this._eventList.length > MAX_EVENTS$3) {
|
|
6602
|
-
this._eventList.shift();
|
|
6603
|
-
}
|
|
6598
|
+
this._eventList.push(newEvent);
|
|
6599
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
6600
|
+
if (this._eventList.length > MAX_EVENTS$4) {
|
|
6601
|
+
this._eventList.shift();
|
|
6604
6602
|
}
|
|
6605
6603
|
}
|
|
6606
6604
|
/**
|
|
@@ -7001,7 +6999,7 @@ const columns$2 = [
|
|
|
7001
6999
|
},
|
|
7002
7000
|
];
|
|
7003
7001
|
/** Maximum number of events to store in schedule reports */
|
|
7004
|
-
const MAX_EVENTS$
|
|
7002
|
+
const MAX_EVENTS$3 = 250;
|
|
7005
7003
|
/**
|
|
7006
7004
|
* Storage class for accumulating scheduled signal events per strategy.
|
|
7007
7005
|
* Maintains a chronological list of scheduled and cancelled events.
|
|
@@ -7030,21 +7028,45 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7030
7028
|
stopLoss: data.signal.priceStopLoss,
|
|
7031
7029
|
});
|
|
7032
7030
|
// Trim queue if exceeded MAX_EVENTS
|
|
7033
|
-
if (this._eventList.length > MAX_EVENTS$
|
|
7031
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
7032
|
+
this._eventList.shift();
|
|
7033
|
+
}
|
|
7034
|
+
}
|
|
7035
|
+
/**
|
|
7036
|
+
* Adds an opened event to the storage.
|
|
7037
|
+
*
|
|
7038
|
+
* @param data - Opened tick result
|
|
7039
|
+
*/
|
|
7040
|
+
addOpenedEvent(data) {
|
|
7041
|
+
const durationMs = data.signal.pendingAt - data.signal.scheduledAt;
|
|
7042
|
+
const durationMin = Math.round(durationMs / 60000);
|
|
7043
|
+
const newEvent = {
|
|
7044
|
+
timestamp: data.signal.pendingAt,
|
|
7045
|
+
action: "opened",
|
|
7046
|
+
symbol: data.signal.symbol,
|
|
7047
|
+
signalId: data.signal.id,
|
|
7048
|
+
position: data.signal.position,
|
|
7049
|
+
note: data.signal.note,
|
|
7050
|
+
currentPrice: data.currentPrice,
|
|
7051
|
+
priceOpen: data.signal.priceOpen,
|
|
7052
|
+
takeProfit: data.signal.priceTakeProfit,
|
|
7053
|
+
stopLoss: data.signal.priceStopLoss,
|
|
7054
|
+
duration: durationMin,
|
|
7055
|
+
};
|
|
7056
|
+
this._eventList.push(newEvent);
|
|
7057
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
7058
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
7034
7059
|
this._eventList.shift();
|
|
7035
7060
|
}
|
|
7036
7061
|
}
|
|
7037
7062
|
/**
|
|
7038
|
-
*
|
|
7039
|
-
* Replaces the previous event with the same signalId.
|
|
7063
|
+
* Adds a cancelled event to the storage.
|
|
7040
7064
|
*
|
|
7041
7065
|
* @param data - Cancelled tick result
|
|
7042
7066
|
*/
|
|
7043
7067
|
addCancelledEvent(data) {
|
|
7044
7068
|
const durationMs = data.closeTimestamp - data.signal.scheduledAt;
|
|
7045
7069
|
const durationMin = Math.round(durationMs / 60000);
|
|
7046
|
-
// Find existing event with the same signalId
|
|
7047
|
-
const existingIndex = this._eventList.findIndex((event) => event.signalId === data.signal.id);
|
|
7048
7070
|
const newEvent = {
|
|
7049
7071
|
timestamp: data.closeTimestamp,
|
|
7050
7072
|
action: "cancelled",
|
|
@@ -7059,16 +7081,10 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7059
7081
|
closeTimestamp: data.closeTimestamp,
|
|
7060
7082
|
duration: durationMin,
|
|
7061
7083
|
};
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
else {
|
|
7067
|
-
this._eventList.push(newEvent);
|
|
7068
|
-
// Trim queue if exceeded MAX_EVENTS
|
|
7069
|
-
if (this._eventList.length > MAX_EVENTS$2) {
|
|
7070
|
-
this._eventList.shift();
|
|
7071
|
-
}
|
|
7084
|
+
this._eventList.push(newEvent);
|
|
7085
|
+
// Trim queue if exceeded MAX_EVENTS
|
|
7086
|
+
if (this._eventList.length > MAX_EVENTS$3) {
|
|
7087
|
+
this._eventList.shift();
|
|
7072
7088
|
}
|
|
7073
7089
|
}
|
|
7074
7090
|
/**
|
|
@@ -7082,29 +7098,44 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7082
7098
|
eventList: [],
|
|
7083
7099
|
totalEvents: 0,
|
|
7084
7100
|
totalScheduled: 0,
|
|
7101
|
+
totalOpened: 0,
|
|
7085
7102
|
totalCancelled: 0,
|
|
7086
7103
|
cancellationRate: null,
|
|
7104
|
+
activationRate: null,
|
|
7087
7105
|
avgWaitTime: null,
|
|
7106
|
+
avgActivationTime: null,
|
|
7088
7107
|
};
|
|
7089
7108
|
}
|
|
7090
7109
|
const scheduledEvents = this._eventList.filter((e) => e.action === "scheduled");
|
|
7110
|
+
const openedEvents = this._eventList.filter((e) => e.action === "opened");
|
|
7091
7111
|
const cancelledEvents = this._eventList.filter((e) => e.action === "cancelled");
|
|
7092
7112
|
const totalScheduled = scheduledEvents.length;
|
|
7113
|
+
const totalOpened = openedEvents.length;
|
|
7093
7114
|
const totalCancelled = cancelledEvents.length;
|
|
7094
7115
|
// Calculate cancellation rate
|
|
7095
7116
|
const cancellationRate = totalScheduled > 0 ? (totalCancelled / totalScheduled) * 100 : null;
|
|
7117
|
+
// Calculate activation rate
|
|
7118
|
+
const activationRate = totalScheduled > 0 ? (totalOpened / totalScheduled) * 100 : null;
|
|
7096
7119
|
// Calculate average wait time for cancelled signals
|
|
7097
7120
|
const avgWaitTime = totalCancelled > 0
|
|
7098
7121
|
? cancelledEvents.reduce((sum, e) => sum + (e.duration || 0), 0) /
|
|
7099
7122
|
totalCancelled
|
|
7100
7123
|
: null;
|
|
7124
|
+
// Calculate average activation time for opened signals
|
|
7125
|
+
const avgActivationTime = totalOpened > 0
|
|
7126
|
+
? openedEvents.reduce((sum, e) => sum + (e.duration || 0), 0) /
|
|
7127
|
+
totalOpened
|
|
7128
|
+
: null;
|
|
7101
7129
|
return {
|
|
7102
7130
|
eventList: this._eventList,
|
|
7103
7131
|
totalEvents: this._eventList.length,
|
|
7104
7132
|
totalScheduled,
|
|
7133
|
+
totalOpened,
|
|
7105
7134
|
totalCancelled,
|
|
7106
7135
|
cancellationRate,
|
|
7136
|
+
activationRate,
|
|
7107
7137
|
avgWaitTime,
|
|
7138
|
+
avgActivationTime,
|
|
7108
7139
|
};
|
|
7109
7140
|
}
|
|
7110
7141
|
/**
|
|
@@ -7134,8 +7165,11 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
7134
7165
|
"",
|
|
7135
7166
|
`**Total events:** ${stats.totalEvents}`,
|
|
7136
7167
|
`**Scheduled signals:** ${stats.totalScheduled}`,
|
|
7168
|
+
`**Opened signals:** ${stats.totalOpened}`,
|
|
7137
7169
|
`**Cancelled signals:** ${stats.totalCancelled}`,
|
|
7170
|
+
`**Activation rate:** ${stats.activationRate === null ? "N/A" : `${stats.activationRate.toFixed(2)}% (higher is better)`}`,
|
|
7138
7171
|
`**Cancellation rate:** ${stats.cancellationRate === null ? "N/A" : `${stats.cancellationRate.toFixed(2)}% (lower is better)`}`,
|
|
7172
|
+
`**Average activation time:** ${stats.avgActivationTime === null ? "N/A" : `${stats.avgActivationTime.toFixed(2)} minutes`}`,
|
|
7139
7173
|
`**Average wait time (cancelled):** ${stats.avgWaitTime === null ? "N/A" : `${stats.avgWaitTime.toFixed(2)} minutes`}`
|
|
7140
7174
|
].join("\n");
|
|
7141
7175
|
}
|
|
@@ -7191,10 +7225,10 @@ class ScheduleMarkdownService {
|
|
|
7191
7225
|
*/
|
|
7192
7226
|
this.getStorage = memoize(([symbol, strategyName]) => `${symbol}:${strategyName}`, () => new ReportStorage$2());
|
|
7193
7227
|
/**
|
|
7194
|
-
* Processes tick events and accumulates scheduled/cancelled events.
|
|
7195
|
-
* Should be called from
|
|
7228
|
+
* Processes tick events and accumulates scheduled/opened/cancelled events.
|
|
7229
|
+
* Should be called from signalEmitter subscription.
|
|
7196
7230
|
*
|
|
7197
|
-
* Processes only scheduled and cancelled event types.
|
|
7231
|
+
* Processes only scheduled, opened and cancelled event types.
|
|
7198
7232
|
*
|
|
7199
7233
|
* @param data - Tick result from strategy execution
|
|
7200
7234
|
*
|
|
@@ -7212,6 +7246,13 @@ class ScheduleMarkdownService {
|
|
|
7212
7246
|
if (data.action === "scheduled") {
|
|
7213
7247
|
storage.addScheduledEvent(data);
|
|
7214
7248
|
}
|
|
7249
|
+
else if (data.action === "opened") {
|
|
7250
|
+
// Check if this opened signal was previously scheduled
|
|
7251
|
+
// by checking if signal has scheduledAt != pendingAt
|
|
7252
|
+
if (data.signal.scheduledAt !== data.signal.pendingAt) {
|
|
7253
|
+
storage.addOpenedEvent(data);
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7215
7256
|
else if (data.action === "cancelled") {
|
|
7216
7257
|
storage.addCancelledEvent(data);
|
|
7217
7258
|
}
|
|
@@ -7349,7 +7390,7 @@ function percentile(sortedArray, p) {
|
|
|
7349
7390
|
return sortedArray[Math.max(0, index)];
|
|
7350
7391
|
}
|
|
7351
7392
|
/** Maximum number of performance events to store per strategy */
|
|
7352
|
-
const MAX_EVENTS$
|
|
7393
|
+
const MAX_EVENTS$2 = 10000;
|
|
7353
7394
|
/**
|
|
7354
7395
|
* Storage class for accumulating performance metrics per strategy.
|
|
7355
7396
|
* Maintains a list of all performance events and provides aggregated statistics.
|
|
@@ -7367,7 +7408,7 @@ class PerformanceStorage {
|
|
|
7367
7408
|
addEvent(event) {
|
|
7368
7409
|
this._events.push(event);
|
|
7369
7410
|
// Trim queue if exceeded MAX_EVENTS (keep most recent)
|
|
7370
|
-
if (this._events.length > MAX_EVENTS$
|
|
7411
|
+
if (this._events.length > MAX_EVENTS$2) {
|
|
7371
7412
|
this._events.shift();
|
|
7372
7413
|
}
|
|
7373
7414
|
}
|
|
@@ -8279,6 +8320,8 @@ const columns$1 = [
|
|
|
8279
8320
|
format: (data) => data.totalTrades.toString(),
|
|
8280
8321
|
},
|
|
8281
8322
|
];
|
|
8323
|
+
/** Maximum number of signals to store per symbol in heatmap reports */
|
|
8324
|
+
const MAX_EVENTS$1 = 250;
|
|
8282
8325
|
/**
|
|
8283
8326
|
* Storage class for accumulating closed signals per strategy and generating heatmap.
|
|
8284
8327
|
* Maintains symbol-level statistics and provides portfolio-wide metrics.
|
|
@@ -8298,7 +8341,12 @@ class HeatmapStorage {
|
|
|
8298
8341
|
if (!this.symbolData.has(symbol)) {
|
|
8299
8342
|
this.symbolData.set(symbol, []);
|
|
8300
8343
|
}
|
|
8301
|
-
this.symbolData.get(symbol)
|
|
8344
|
+
const signals = this.symbolData.get(symbol);
|
|
8345
|
+
signals.push(data);
|
|
8346
|
+
// Trim queue if exceeded MAX_EVENTS per symbol
|
|
8347
|
+
if (signals.length > MAX_EVENTS$1) {
|
|
8348
|
+
signals.shift();
|
|
8349
|
+
}
|
|
8302
8350
|
}
|
|
8303
8351
|
/**
|
|
8304
8352
|
* Calculates statistics for a single symbol.
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -4079,13 +4079,13 @@ declare class LiveMarkdownService {
|
|
|
4079
4079
|
|
|
4080
4080
|
/**
|
|
4081
4081
|
* Unified scheduled signal event data for report generation.
|
|
4082
|
-
* Contains all information about scheduled and cancelled events.
|
|
4082
|
+
* Contains all information about scheduled, opened and cancelled events.
|
|
4083
4083
|
*/
|
|
4084
4084
|
interface ScheduledEvent {
|
|
4085
4085
|
/** Event timestamp in milliseconds (scheduledAt for scheduled/cancelled events) */
|
|
4086
4086
|
timestamp: number;
|
|
4087
4087
|
/** Event action type */
|
|
4088
|
-
action: "scheduled" | "cancelled";
|
|
4088
|
+
action: "scheduled" | "opened" | "cancelled";
|
|
4089
4089
|
/** Trading pair symbol */
|
|
4090
4090
|
symbol: string;
|
|
4091
4091
|
/** Signal ID */
|
|
@@ -4104,13 +4104,13 @@ interface ScheduledEvent {
|
|
|
4104
4104
|
stopLoss: number;
|
|
4105
4105
|
/** Close timestamp (only for cancelled) */
|
|
4106
4106
|
closeTimestamp?: number;
|
|
4107
|
-
/** Duration in minutes (only for cancelled) */
|
|
4107
|
+
/** Duration in minutes (only for cancelled/opened) */
|
|
4108
4108
|
duration?: number;
|
|
4109
4109
|
}
|
|
4110
4110
|
/**
|
|
4111
4111
|
* Statistical data calculated from scheduled signals.
|
|
4112
4112
|
*
|
|
4113
|
-
* Provides metrics for scheduled signal tracking and cancellation analysis.
|
|
4113
|
+
* Provides metrics for scheduled signal tracking, activation and cancellation analysis.
|
|
4114
4114
|
*
|
|
4115
4115
|
* @example
|
|
4116
4116
|
* ```typescript
|
|
@@ -4118,10 +4118,11 @@ interface ScheduledEvent {
|
|
|
4118
4118
|
*
|
|
4119
4119
|
* console.log(`Total events: ${stats.totalEvents}`);
|
|
4120
4120
|
* console.log(`Scheduled signals: ${stats.totalScheduled}`);
|
|
4121
|
+
* console.log(`Opened signals: ${stats.totalOpened}`);
|
|
4121
4122
|
* console.log(`Cancelled signals: ${stats.totalCancelled}`);
|
|
4122
4123
|
* console.log(`Cancellation rate: ${stats.cancellationRate}%`);
|
|
4123
4124
|
*
|
|
4124
|
-
* // Access raw event data (includes scheduled, cancelled)
|
|
4125
|
+
* // Access raw event data (includes scheduled, opened, cancelled)
|
|
4125
4126
|
* stats.eventList.forEach(event => {
|
|
4126
4127
|
* if (event.action === "cancelled") {
|
|
4127
4128
|
* console.log(`Cancelled signal: ${event.signalId}`);
|
|
@@ -4130,18 +4131,24 @@ interface ScheduledEvent {
|
|
|
4130
4131
|
* ```
|
|
4131
4132
|
*/
|
|
4132
4133
|
interface ScheduleStatistics {
|
|
4133
|
-
/** Array of all scheduled/cancelled events with full details */
|
|
4134
|
+
/** Array of all scheduled/opened/cancelled events with full details */
|
|
4134
4135
|
eventList: ScheduledEvent[];
|
|
4135
|
-
/** Total number of all events (includes scheduled, cancelled) */
|
|
4136
|
+
/** Total number of all events (includes scheduled, opened, cancelled) */
|
|
4136
4137
|
totalEvents: number;
|
|
4137
4138
|
/** Total number of scheduled signals */
|
|
4138
4139
|
totalScheduled: number;
|
|
4140
|
+
/** Total number of opened signals (activated from scheduled) */
|
|
4141
|
+
totalOpened: number;
|
|
4139
4142
|
/** Total number of cancelled signals */
|
|
4140
4143
|
totalCancelled: number;
|
|
4141
4144
|
/** Cancellation rate as percentage (0-100), null if no scheduled signals. Lower is better. */
|
|
4142
4145
|
cancellationRate: number | null;
|
|
4146
|
+
/** Activation rate as percentage (0-100), null if no scheduled signals. Higher is better. */
|
|
4147
|
+
activationRate: number | null;
|
|
4143
4148
|
/** Average waiting time for cancelled signals in minutes, null if no cancelled signals */
|
|
4144
4149
|
avgWaitTime: number | null;
|
|
4150
|
+
/** Average waiting time for opened signals in minutes, null if no opened signals */
|
|
4151
|
+
avgActivationTime: number | null;
|
|
4145
4152
|
}
|
|
4146
4153
|
/**
|
|
4147
4154
|
* Service for generating and saving scheduled signals markdown reports.
|
|
@@ -4173,10 +4180,10 @@ declare class ScheduleMarkdownService {
|
|
|
4173
4180
|
*/
|
|
4174
4181
|
private getStorage;
|
|
4175
4182
|
/**
|
|
4176
|
-
* Processes tick events and accumulates scheduled/cancelled events.
|
|
4177
|
-
* Should be called from
|
|
4183
|
+
* Processes tick events and accumulates scheduled/opened/cancelled events.
|
|
4184
|
+
* Should be called from signalEmitter subscription.
|
|
4178
4185
|
*
|
|
4179
|
-
* Processes only scheduled and cancelled event types.
|
|
4186
|
+
* Processes only scheduled, opened and cancelled event types.
|
|
4180
4187
|
*
|
|
4181
4188
|
* @param data - Tick result from strategy execution
|
|
4182
4189
|
*
|
|
@@ -6708,7 +6715,23 @@ declare class ClientExchange implements IExchange {
|
|
|
6708
6715
|
* @throws Error if no candles available
|
|
6709
6716
|
*/
|
|
6710
6717
|
getAveragePrice(symbol: string): Promise<number>;
|
|
6718
|
+
/**
|
|
6719
|
+
* Formats quantity according to exchange-specific rules for the given symbol.
|
|
6720
|
+
* Applies proper decimal precision and rounding based on symbol's lot size filters.
|
|
6721
|
+
*
|
|
6722
|
+
* @param symbol - Trading pair symbol
|
|
6723
|
+
* @param quantity - Raw quantity to format
|
|
6724
|
+
* @returns Promise resolving to formatted quantity as string
|
|
6725
|
+
*/
|
|
6711
6726
|
formatQuantity(symbol: string, quantity: number): Promise<string>;
|
|
6727
|
+
/**
|
|
6728
|
+
* Formats price according to exchange-specific rules for the given symbol.
|
|
6729
|
+
* Applies proper decimal precision and rounding based on symbol's price filters.
|
|
6730
|
+
*
|
|
6731
|
+
* @param symbol - Trading pair symbol
|
|
6732
|
+
* @param price - Raw price to format
|
|
6733
|
+
* @returns Promise resolving to formatted price as string
|
|
6734
|
+
*/
|
|
6712
6735
|
formatPrice(symbol: string, price: number): Promise<string>;
|
|
6713
6736
|
}
|
|
6714
6737
|
|