hedgequantx 2.4.34 → 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 +74 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.4.34",
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,20 +444,31 @@ 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
 
@@ -464,9 +489,15 @@ const showStats = async (service) => {
464
489
  const timestamp = trade.creationTimestamp || trade.timestamp;
465
490
  const time = timestamp ? new Date(timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) : '--:--';
466
491
  const symbol = extractSymbol(trade.contractId || trade.symbol);
467
- const price = (trade.price || 0).toFixed(2);
468
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
469
497
  const pnlText = pnl >= 0 ? `+$${pnl.toFixed(0)}` : `-$${Math.abs(pnl).toFixed(0)}`;
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
+
470
501
  // For completed trades, show the original direction (opposite of exit side)
471
502
  const exitSide = trade.side; // 0=BUY exit means was SHORT, 1=SELL exit means was LONG
472
503
  const tradeSide = exitSide === 0 ? 'SHORT' : 'LONG';
@@ -475,16 +506,29 @@ const showStats = async (service) => {
475
506
  // Build row with exact widths
476
507
  const timeStr = time.padEnd(colTime);
477
508
  const symbolStr = symbol.padEnd(colSymbol);
478
- const priceStr = price.padEnd(colPrice);
479
- const pnlStr = pnlText.padEnd(colPnl);
480
509
  const sideStr = tradeSide.padEnd(colSide);
481
- const accountStr = accountName.padEnd(colAccount - 2);
510
+ const pnlStr = pnlText.padEnd(colPnl);
511
+ const feesStr = feesText.padEnd(colFees);
512
+ const netStr = netText.padEnd(colNet);
513
+ const accountStr = accountName.padEnd(colAccount);
482
514
 
483
515
  // Colored versions
484
516
  const pnlColored = pnl >= 0 ? chalk.green(pnlStr) : chalk.red(pnlStr);
517
+ const feesColored = chalk.yellow(feesStr);
518
+ const netColored = netPnl >= 0 ? chalk.green(netStr) : chalk.red(netStr);
485
519
  const sideColored = tradeSide === 'LONG' ? chalk.green(sideStr) : chalk.red(sideStr);
486
520
 
487
- 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('| ');
488
532
  console.log(chalk.cyan('\u2551') + row + chalk.cyan('\u2551'));
489
533
  }
490
534