hedgequantx 2.4.34 → 2.4.36
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/package.json +1 -1
- package/src/pages/stats.js +90 -39
package/package.json
CHANGED
package/src/pages/stats.js
CHANGED
|
@@ -187,7 +187,8 @@ const showStats = async (service) => {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
// ========== AGGREGATE STATS FROM TRADE HISTORY (API DATA) ==========
|
|
190
|
-
// Calculate stats from
|
|
190
|
+
// Calculate stats from COMPLETED trades only (those with P&L != 0)
|
|
191
|
+
// This matches what we display in TRADES HISTORY
|
|
191
192
|
|
|
192
193
|
let stats = {
|
|
193
194
|
totalTrades: 0, winningTrades: 0, losingTrades: 0,
|
|
@@ -197,44 +198,60 @@ const showStats = async (service) => {
|
|
|
197
198
|
longTrades: 0, shortTrades: 0, longWins: 0, shortWins: 0
|
|
198
199
|
};
|
|
199
200
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
// Filter to completed trades only (P&L != 0, not null)
|
|
202
|
+
const completedTrades = allTrades.filter(t => {
|
|
203
|
+
const pnl = t.profitAndLoss || t.pnl;
|
|
204
|
+
return pnl !== null && pnl !== undefined && pnl !== 0;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Calculate stats from completed trades only
|
|
208
|
+
// Include fees in P&L calculations to match TopStep
|
|
209
|
+
if (completedTrades.length > 0) {
|
|
210
|
+
stats.totalTrades = completedTrades.length;
|
|
203
211
|
let consecutiveWins = 0, consecutiveLosses = 0;
|
|
204
212
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
213
|
+
// Sort by time for consecutive win/loss calculation
|
|
214
|
+
const sortedTrades = [...completedTrades].sort((a, b) => {
|
|
215
|
+
const timeA = new Date(a.creationTimestamp || a.timestamp || 0).getTime();
|
|
216
|
+
const timeB = new Date(b.creationTimestamp || b.timestamp || 0).getTime();
|
|
217
|
+
return timeA - timeB;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
for (const trade of sortedTrades) {
|
|
221
|
+
const grossPnl = trade.profitAndLoss || trade.pnl || 0;
|
|
222
|
+
const fees = Math.abs(trade.fees || trade.commission || 0);
|
|
223
|
+
const netPnl = grossPnl - fees; // Net P&L after fees (like TopStep)
|
|
208
224
|
const size = trade.size || trade.quantity || 1;
|
|
209
|
-
const
|
|
225
|
+
const exitSide = trade.side; // 0=BUY exit (was SHORT), 1=SELL exit (was LONG)
|
|
210
226
|
|
|
211
227
|
stats.totalVolume += Math.abs(size);
|
|
212
228
|
|
|
213
|
-
//
|
|
214
|
-
|
|
229
|
+
// Determine original trade direction from exit side
|
|
230
|
+
// Exit side 0 = BUY to close = was SHORT
|
|
231
|
+
// Exit side 1 = SELL to close = was LONG
|
|
232
|
+
if (exitSide === 1) {
|
|
215
233
|
stats.longTrades++;
|
|
216
|
-
if (
|
|
217
|
-
} else if (
|
|
234
|
+
if (netPnl > 0) stats.longWins++;
|
|
235
|
+
} else if (exitSide === 0) {
|
|
218
236
|
stats.shortTrades++;
|
|
219
|
-
if (
|
|
237
|
+
if (netPnl > 0) stats.shortWins++;
|
|
220
238
|
}
|
|
221
239
|
|
|
222
|
-
if (
|
|
240
|
+
if (netPnl > 0) {
|
|
223
241
|
stats.winningTrades++;
|
|
224
|
-
stats.totalWinAmount +=
|
|
242
|
+
stats.totalWinAmount += netPnl;
|
|
225
243
|
consecutiveWins++;
|
|
226
244
|
consecutiveLosses = 0;
|
|
227
245
|
if (consecutiveWins > stats.maxConsecutiveWins) stats.maxConsecutiveWins = consecutiveWins;
|
|
228
|
-
if (
|
|
229
|
-
} else if (
|
|
246
|
+
if (netPnl > stats.bestTrade) stats.bestTrade = netPnl;
|
|
247
|
+
} else if (netPnl < 0) {
|
|
230
248
|
stats.losingTrades++;
|
|
231
|
-
stats.totalLossAmount += Math.abs(
|
|
249
|
+
stats.totalLossAmount += Math.abs(netPnl);
|
|
232
250
|
consecutiveLosses++;
|
|
233
251
|
consecutiveWins = 0;
|
|
234
252
|
if (consecutiveLosses > stats.maxConsecutiveLosses) stats.maxConsecutiveLosses = consecutiveLosses;
|
|
235
|
-
if (
|
|
253
|
+
if (netPnl < stats.worstTrade) stats.worstTrade = netPnl;
|
|
236
254
|
}
|
|
237
|
-
// pnl === 0 trades are neither win nor loss
|
|
238
255
|
}
|
|
239
256
|
}
|
|
240
257
|
|
|
@@ -257,8 +274,12 @@ const showStats = async (service) => {
|
|
|
257
274
|
const longWinRate = stats.longTrades > 0 ? ((stats.longWins / stats.longTrades) * 100).toFixed(1) : 'N/A';
|
|
258
275
|
const shortWinRate = stats.shortTrades > 0 ? ((stats.shortWins / stats.shortTrades) * 100).toFixed(1) : 'N/A';
|
|
259
276
|
|
|
260
|
-
// Quantitative metrics (calculated from
|
|
261
|
-
const tradePnLs =
|
|
277
|
+
// Quantitative metrics (calculated from completed trades only, with fees)
|
|
278
|
+
const tradePnLs = completedTrades.map(t => {
|
|
279
|
+
const grossPnl = t.profitAndLoss || t.pnl || 0;
|
|
280
|
+
const fees = Math.abs(t.fees || t.commission || 0);
|
|
281
|
+
return grossPnl - fees; // Net P&L
|
|
282
|
+
});
|
|
262
283
|
const avgReturn = tradePnLs.length > 0 ? tradePnLs.reduce((a, b) => a + b, 0) / tradePnLs.length : 0;
|
|
263
284
|
|
|
264
285
|
// Standard deviation
|
|
@@ -430,20 +451,31 @@ const showStats = async (service) => {
|
|
|
430
451
|
};
|
|
431
452
|
|
|
432
453
|
if (allTrades.length > 0) {
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
const colSymbol = 12;
|
|
438
|
-
const colPrice = 12;
|
|
439
|
-
const colPnl = 12;
|
|
454
|
+
// Column widths - total must equal innerWidth
|
|
455
|
+
// Format: " Time | Symbol | Side | P&L | Fees | Net | Account... "
|
|
456
|
+
const colTime = 9;
|
|
457
|
+
const colSymbol = 10;
|
|
440
458
|
const colSide = 6;
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const
|
|
459
|
+
const colPnl = 10;
|
|
460
|
+
const colFees = 8;
|
|
461
|
+
const colNet = 10;
|
|
462
|
+
// Each column has "| " after it (2 chars), plus leading space (1 char)
|
|
463
|
+
const fixedCols = colTime + colSymbol + colSide + colPnl + colFees + colNet;
|
|
464
|
+
const separatorChars = 6 * 2; // 6 "| " separators
|
|
465
|
+
const leadingSpace = 1;
|
|
466
|
+
const colAccount = innerWidth - fixedCols - separatorChars - leadingSpace;
|
|
444
467
|
|
|
445
|
-
// Header
|
|
446
|
-
const
|
|
468
|
+
// Header - build with exact spacing
|
|
469
|
+
const headerParts = [
|
|
470
|
+
' ' + 'Time'.padEnd(colTime),
|
|
471
|
+
'Symbol'.padEnd(colSymbol),
|
|
472
|
+
'Side'.padEnd(colSide),
|
|
473
|
+
'P&L'.padEnd(colPnl),
|
|
474
|
+
'Fees'.padEnd(colFees),
|
|
475
|
+
'Net'.padEnd(colNet),
|
|
476
|
+
'Account'.padEnd(colAccount)
|
|
477
|
+
];
|
|
478
|
+
const header = headerParts.join('| ');
|
|
447
479
|
console.log(chalk.cyan('\u2551') + chalk.white(header) + chalk.cyan('\u2551'));
|
|
448
480
|
console.log(chalk.cyan('\u255F') + chalk.cyan('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2562'));
|
|
449
481
|
|
|
@@ -464,9 +496,15 @@ const showStats = async (service) => {
|
|
|
464
496
|
const timestamp = trade.creationTimestamp || trade.timestamp;
|
|
465
497
|
const time = timestamp ? new Date(timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) : '--:--';
|
|
466
498
|
const symbol = extractSymbol(trade.contractId || trade.symbol);
|
|
467
|
-
const price = (trade.price || 0).toFixed(2);
|
|
468
499
|
const pnl = trade.profitAndLoss || trade.pnl || 0;
|
|
500
|
+
const fees = trade.fees || trade.commission || 0;
|
|
501
|
+
const netPnl = pnl - Math.abs(fees);
|
|
502
|
+
|
|
503
|
+
// Format values
|
|
469
504
|
const pnlText = pnl >= 0 ? `+$${pnl.toFixed(0)}` : `-$${Math.abs(pnl).toFixed(0)}`;
|
|
505
|
+
const feesText = fees !== 0 ? `-$${Math.abs(fees).toFixed(2)}` : '$0';
|
|
506
|
+
const netText = netPnl >= 0 ? `+$${netPnl.toFixed(0)}` : `-$${Math.abs(netPnl).toFixed(0)}`;
|
|
507
|
+
|
|
470
508
|
// For completed trades, show the original direction (opposite of exit side)
|
|
471
509
|
const exitSide = trade.side; // 0=BUY exit means was SHORT, 1=SELL exit means was LONG
|
|
472
510
|
const tradeSide = exitSide === 0 ? 'SHORT' : 'LONG';
|
|
@@ -475,16 +513,29 @@ const showStats = async (service) => {
|
|
|
475
513
|
// Build row with exact widths
|
|
476
514
|
const timeStr = time.padEnd(colTime);
|
|
477
515
|
const symbolStr = symbol.padEnd(colSymbol);
|
|
478
|
-
const priceStr = price.padEnd(colPrice);
|
|
479
|
-
const pnlStr = pnlText.padEnd(colPnl);
|
|
480
516
|
const sideStr = tradeSide.padEnd(colSide);
|
|
481
|
-
const
|
|
517
|
+
const pnlStr = pnlText.padEnd(colPnl);
|
|
518
|
+
const feesStr = feesText.padEnd(colFees);
|
|
519
|
+
const netStr = netText.padEnd(colNet);
|
|
520
|
+
const accountStr = accountName.padEnd(colAccount);
|
|
482
521
|
|
|
483
522
|
// Colored versions
|
|
484
523
|
const pnlColored = pnl >= 0 ? chalk.green(pnlStr) : chalk.red(pnlStr);
|
|
524
|
+
const feesColored = chalk.yellow(feesStr);
|
|
525
|
+
const netColored = netPnl >= 0 ? chalk.green(netStr) : chalk.red(netStr);
|
|
485
526
|
const sideColored = tradeSide === 'LONG' ? chalk.green(sideStr) : chalk.red(sideStr);
|
|
486
527
|
|
|
487
|
-
|
|
528
|
+
// Build row with same format as header
|
|
529
|
+
const rowParts = [
|
|
530
|
+
' ' + timeStr,
|
|
531
|
+
symbolStr,
|
|
532
|
+
sideColored,
|
|
533
|
+
pnlColored,
|
|
534
|
+
feesColored,
|
|
535
|
+
netColored,
|
|
536
|
+
accountStr
|
|
537
|
+
];
|
|
538
|
+
const row = rowParts.join('| ');
|
|
488
539
|
console.log(chalk.cyan('\u2551') + row + chalk.cyan('\u2551'));
|
|
489
540
|
}
|
|
490
541
|
|