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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/pages/stats.js +90 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.4.34",
3
+ "version": "2.4.36",
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,44 +198,60 @@ 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
+ // 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
- for (const trade of allTrades) {
206
- // P&L comes directly from API response
207
- const pnl = trade.profitAndLoss || trade.pnl || 0;
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 side = trade.side;
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
- // Side: 0 = Buy/Long, 1 = Sell/Short (ProjectX API format)
214
- if (side === 0) {
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 (pnl > 0) stats.longWins++;
217
- } else if (side === 1) {
234
+ if (netPnl > 0) stats.longWins++;
235
+ } else if (exitSide === 0) {
218
236
  stats.shortTrades++;
219
- if (pnl > 0) stats.shortWins++;
237
+ if (netPnl > 0) stats.shortWins++;
220
238
  }
221
239
 
222
- if (pnl > 0) {
240
+ if (netPnl > 0) {
223
241
  stats.winningTrades++;
224
- stats.totalWinAmount += pnl;
242
+ stats.totalWinAmount += netPnl;
225
243
  consecutiveWins++;
226
244
  consecutiveLosses = 0;
227
245
  if (consecutiveWins > stats.maxConsecutiveWins) stats.maxConsecutiveWins = consecutiveWins;
228
- if (pnl > stats.bestTrade) stats.bestTrade = pnl;
229
- } else if (pnl < 0) {
246
+ if (netPnl > stats.bestTrade) stats.bestTrade = netPnl;
247
+ } else if (netPnl < 0) {
230
248
  stats.losingTrades++;
231
- stats.totalLossAmount += Math.abs(pnl);
249
+ stats.totalLossAmount += Math.abs(netPnl);
232
250
  consecutiveLosses++;
233
251
  consecutiveWins = 0;
234
252
  if (consecutiveLosses > stats.maxConsecutiveLosses) stats.maxConsecutiveLosses = consecutiveLosses;
235
- if (pnl < stats.worstTrade) stats.worstTrade = pnl;
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 API trade data)
261
- const tradePnLs = allTrades.map(t => t.profitAndLoss || t.pnl || 0);
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
- // 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;
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 separators = 15; // 5 separators " | " = 5*3
442
- const fixedWidth = colTime + colSymbol + colPrice + colPnl + colSide + separators;
443
- const colAccount = Math.max(10, innerWidth - fixedWidth);
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 header = ` ${'Time'.padEnd(colTime)}| ${'Symbol'.padEnd(colSymbol)}| ${'Price'.padEnd(colPrice)}| ${'P&L'.padEnd(colPnl)}| ${'Side'.padEnd(colSide)}| ${'Account'.padEnd(colAccount - 2)}`;
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 accountStr = accountName.padEnd(colAccount - 2);
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
- const row = ` ${timeStr}| ${symbolStr}| ${priceStr}| ${pnlColored}| ${sideColored}| ${accountStr}`;
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