hedgequantx 2.4.33 → 2.4.35
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 +91 -34
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,24 +198,38 @@ 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
|
+
if (completedTrades.length > 0) {
|
|
209
|
+
stats.totalTrades = completedTrades.length;
|
|
203
210
|
let consecutiveWins = 0, consecutiveLosses = 0;
|
|
204
211
|
|
|
205
|
-
|
|
206
|
-
|
|
212
|
+
// Sort by time for consecutive win/loss calculation
|
|
213
|
+
const sortedTrades = [...completedTrades].sort((a, b) => {
|
|
214
|
+
const timeA = new Date(a.creationTimestamp || a.timestamp || 0).getTime();
|
|
215
|
+
const timeB = new Date(b.creationTimestamp || b.timestamp || 0).getTime();
|
|
216
|
+
return timeA - timeB;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
for (const trade of sortedTrades) {
|
|
207
220
|
const pnl = trade.profitAndLoss || trade.pnl || 0;
|
|
208
221
|
const size = trade.size || trade.quantity || 1;
|
|
209
|
-
const
|
|
222
|
+
const exitSide = trade.side; // 0=BUY exit (was SHORT), 1=SELL exit (was LONG)
|
|
210
223
|
|
|
211
224
|
stats.totalVolume += Math.abs(size);
|
|
212
225
|
|
|
213
|
-
//
|
|
214
|
-
|
|
226
|
+
// Determine original trade direction from exit side
|
|
227
|
+
// Exit side 0 = BUY to close = was SHORT
|
|
228
|
+
// Exit side 1 = SELL to close = was LONG
|
|
229
|
+
if (exitSide === 1) {
|
|
215
230
|
stats.longTrades++;
|
|
216
231
|
if (pnl > 0) stats.longWins++;
|
|
217
|
-
} else if (
|
|
232
|
+
} else if (exitSide === 0) {
|
|
218
233
|
stats.shortTrades++;
|
|
219
234
|
if (pnl > 0) stats.shortWins++;
|
|
220
235
|
}
|
|
@@ -234,7 +249,6 @@ const showStats = async (service) => {
|
|
|
234
249
|
if (consecutiveLosses > stats.maxConsecutiveLosses) stats.maxConsecutiveLosses = consecutiveLosses;
|
|
235
250
|
if (pnl < stats.worstTrade) stats.worstTrade = pnl;
|
|
236
251
|
}
|
|
237
|
-
// pnl === 0 trades are neither win nor loss
|
|
238
252
|
}
|
|
239
253
|
}
|
|
240
254
|
|
|
@@ -257,8 +271,8 @@ const showStats = async (service) => {
|
|
|
257
271
|
const longWinRate = stats.longTrades > 0 ? ((stats.longWins / stats.longTrades) * 100).toFixed(1) : 'N/A';
|
|
258
272
|
const shortWinRate = stats.shortTrades > 0 ? ((stats.shortWins / stats.shortTrades) * 100).toFixed(1) : 'N/A';
|
|
259
273
|
|
|
260
|
-
// Quantitative metrics (calculated from
|
|
261
|
-
const tradePnLs =
|
|
274
|
+
// Quantitative metrics (calculated from completed trades only)
|
|
275
|
+
const tradePnLs = completedTrades.map(t => t.profitAndLoss || t.pnl || 0);
|
|
262
276
|
const avgReturn = tradePnLs.length > 0 ? tradePnLs.reduce((a, b) => a + b, 0) / tradePnLs.length : 0;
|
|
263
277
|
|
|
264
278
|
// Standard deviation
|
|
@@ -430,25 +444,42 @@ const showStats = async (service) => {
|
|
|
430
444
|
};
|
|
431
445
|
|
|
432
446
|
if (allTrades.length > 0) {
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
const colSymbol = 12;
|
|
438
|
-
const colPrice = 12;
|
|
439
|
-
const colPnl = 12;
|
|
447
|
+
// Column widths - total must equal innerWidth
|
|
448
|
+
// Format: " Time | Symbol | Side | P&L | Fees | Net | Account... "
|
|
449
|
+
const colTime = 9;
|
|
450
|
+
const colSymbol = 10;
|
|
440
451
|
const colSide = 6;
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const
|
|
452
|
+
const colPnl = 10;
|
|
453
|
+
const colFees = 8;
|
|
454
|
+
const colNet = 10;
|
|
455
|
+
// Each column has "| " after it (2 chars), plus leading space (1 char)
|
|
456
|
+
const fixedCols = colTime + colSymbol + colSide + colPnl + colFees + colNet;
|
|
457
|
+
const separatorChars = 6 * 2; // 6 "| " separators
|
|
458
|
+
const leadingSpace = 1;
|
|
459
|
+
const colAccount = innerWidth - fixedCols - separatorChars - leadingSpace;
|
|
444
460
|
|
|
445
|
-
// Header
|
|
446
|
-
const
|
|
461
|
+
// Header - build with exact spacing
|
|
462
|
+
const headerParts = [
|
|
463
|
+
' ' + 'Time'.padEnd(colTime),
|
|
464
|
+
'Symbol'.padEnd(colSymbol),
|
|
465
|
+
'Side'.padEnd(colSide),
|
|
466
|
+
'P&L'.padEnd(colPnl),
|
|
467
|
+
'Fees'.padEnd(colFees),
|
|
468
|
+
'Net'.padEnd(colNet),
|
|
469
|
+
'Account'.padEnd(colAccount)
|
|
470
|
+
];
|
|
471
|
+
const header = headerParts.join('| ');
|
|
447
472
|
console.log(chalk.cyan('\u2551') + chalk.white(header) + chalk.cyan('\u2551'));
|
|
448
473
|
console.log(chalk.cyan('\u255F') + chalk.cyan('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2562'));
|
|
449
474
|
|
|
450
|
-
// Show
|
|
451
|
-
|
|
475
|
+
// Show only COMPLETED trades (with P&L), sorted by time (most recent first)
|
|
476
|
+
// Filter out entry fills (P&L = 0 or null) - only show exit fills with real P&L
|
|
477
|
+
const completedTrades = allTrades.filter(t => {
|
|
478
|
+
const pnl = t.profitAndLoss || t.pnl;
|
|
479
|
+
return pnl !== null && pnl !== undefined && pnl !== 0;
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const sortedTrades = [...completedTrades].sort((a, b) => {
|
|
452
483
|
const timeA = new Date(a.creationTimestamp || a.timestamp || 0).getTime();
|
|
453
484
|
const timeB = new Date(b.creationTimestamp || b.timestamp || 0).getTime();
|
|
454
485
|
return timeB - timeA;
|
|
@@ -458,27 +489,53 @@ const showStats = async (service) => {
|
|
|
458
489
|
const timestamp = trade.creationTimestamp || trade.timestamp;
|
|
459
490
|
const time = timestamp ? new Date(timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) : '--:--';
|
|
460
491
|
const symbol = extractSymbol(trade.contractId || trade.symbol);
|
|
461
|
-
const price = (trade.price || 0).toFixed(2);
|
|
462
492
|
const pnl = trade.profitAndLoss || trade.pnl || 0;
|
|
493
|
+
const fees = trade.fees || trade.commission || 0;
|
|
494
|
+
const netPnl = pnl - Math.abs(fees);
|
|
495
|
+
|
|
496
|
+
// Format values
|
|
463
497
|
const pnlText = pnl >= 0 ? `+$${pnl.toFixed(0)}` : `-$${Math.abs(pnl).toFixed(0)}`;
|
|
464
|
-
const
|
|
498
|
+
const feesText = fees !== 0 ? `-$${Math.abs(fees).toFixed(2)}` : '$0';
|
|
499
|
+
const netText = netPnl >= 0 ? `+$${netPnl.toFixed(0)}` : `-$${Math.abs(netPnl).toFixed(0)}`;
|
|
500
|
+
|
|
501
|
+
// For completed trades, show the original direction (opposite of exit side)
|
|
502
|
+
const exitSide = trade.side; // 0=BUY exit means was SHORT, 1=SELL exit means was LONG
|
|
503
|
+
const tradeSide = exitSide === 0 ? 'SHORT' : 'LONG';
|
|
465
504
|
const accountName = (trade.accountName || 'N/A').substring(0, colAccount - 3);
|
|
466
505
|
|
|
467
506
|
// Build row with exact widths
|
|
468
507
|
const timeStr = time.padEnd(colTime);
|
|
469
508
|
const symbolStr = symbol.padEnd(colSymbol);
|
|
470
|
-
const
|
|
509
|
+
const sideStr = tradeSide.padEnd(colSide);
|
|
471
510
|
const pnlStr = pnlText.padEnd(colPnl);
|
|
472
|
-
const
|
|
473
|
-
const
|
|
511
|
+
const feesStr = feesText.padEnd(colFees);
|
|
512
|
+
const netStr = netText.padEnd(colNet);
|
|
513
|
+
const accountStr = accountName.padEnd(colAccount);
|
|
474
514
|
|
|
475
515
|
// Colored versions
|
|
476
516
|
const pnlColored = pnl >= 0 ? chalk.green(pnlStr) : chalk.red(pnlStr);
|
|
477
|
-
const
|
|
517
|
+
const feesColored = chalk.yellow(feesStr);
|
|
518
|
+
const netColored = netPnl >= 0 ? chalk.green(netStr) : chalk.red(netStr);
|
|
519
|
+
const sideColored = tradeSide === 'LONG' ? chalk.green(sideStr) : chalk.red(sideStr);
|
|
478
520
|
|
|
479
|
-
|
|
521
|
+
// Build row with same format as header
|
|
522
|
+
const rowParts = [
|
|
523
|
+
' ' + timeStr,
|
|
524
|
+
symbolStr,
|
|
525
|
+
sideColored,
|
|
526
|
+
pnlColored,
|
|
527
|
+
feesColored,
|
|
528
|
+
netColored,
|
|
529
|
+
accountStr
|
|
530
|
+
];
|
|
531
|
+
const row = rowParts.join('| ');
|
|
480
532
|
console.log(chalk.cyan('\u2551') + row + chalk.cyan('\u2551'));
|
|
481
533
|
}
|
|
534
|
+
|
|
535
|
+
if (sortedTrades.length === 0) {
|
|
536
|
+
const msg = ' No completed trades yet';
|
|
537
|
+
console.log(chalk.cyan('\u2551') + chalk.gray(msg.padEnd(innerWidth)) + chalk.cyan('\u2551'));
|
|
538
|
+
}
|
|
482
539
|
} else {
|
|
483
540
|
const msg = connectionTypes.rithmic > 0
|
|
484
541
|
? ' No trade history (Rithmic API limitation)'
|