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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/pages/stats.js +91 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.4.33",
3
+ "version": "2.4.35",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -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 actual trades to ensure consistency with displayed trades
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
- // Calculate stats from trade history (100% API data, consistent with displayed trades)
201
- if (allTrades.length > 0) {
202
- stats.totalTrades = allTrades.length;
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
- for (const trade of allTrades) {
206
- // P&L comes directly from API response
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 side = trade.side;
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
- // Side: 0 = Buy/Long, 1 = Sell/Short (ProjectX API format)
214
- if (side === 0) {
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 (side === 1) {
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 API trade data)
261
- const tradePnLs = allTrades.map(t => t.profitAndLoss || t.pnl || 0);
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
- // Calculate column widths to fill the entire row
434
- // Fixed columns: Time(10), Symbol(12), Price(12), P&L(12), Side(6) = 52 + separators(4*3=12) + padding(2) = 66
435
- // Remaining space goes to Account column
436
- const colTime = 10;
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 separators = 15; // 5 separators " | " = 5*3
442
- const fixedWidth = colTime + colSymbol + colPrice + colPnl + colSide + separators;
443
- const colAccount = Math.max(10, innerWidth - fixedWidth);
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 header = ` ${'Time'.padEnd(colTime)}| ${'Symbol'.padEnd(colSymbol)}| ${'Price'.padEnd(colPrice)}| ${'P&L'.padEnd(colPnl)}| ${'Side'.padEnd(colSide)}| ${'Account'.padEnd(colAccount - 2)}`;
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 ALL trades, sorted by time (most recent first)
451
- const sortedTrades = [...allTrades].sort((a, b) => {
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 side = trade.side === 0 ? 'BUY' : trade.side === 1 ? 'SELL' : 'N/A';
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 priceStr = price.padEnd(colPrice);
509
+ const sideStr = tradeSide.padEnd(colSide);
471
510
  const pnlStr = pnlText.padEnd(colPnl);
472
- const sideStr = side.padEnd(colSide);
473
- const accountStr = accountName.padEnd(colAccount - 2);
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 sideColored = trade.side === 0 ? chalk.green(sideStr) : chalk.red(sideStr);
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
- const row = ` ${timeStr}| ${symbolStr}| ${priceStr}| ${pnlColored}| ${sideColored}| ${accountStr}`;
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)'