hedgequantx 2.4.32 → 2.4.33

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.4.32",
3
+ "version": "2.4.33",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -206,6 +206,8 @@ const launchAlgo = async (service, account, contract, config) => {
206
206
  let currentPosition = 0; // Current position qty (+ long, - short)
207
207
  let pendingOrder = false; // Prevent duplicate orders
208
208
  let tickCount = 0;
209
+ let lastTradeCount = 0; // Track number of trades from API
210
+ let lastPositionQty = 0; // Track position changes
209
211
 
210
212
  // Initialize Strategy (M1 is singleton instance)
211
213
  const strategy = M1;
@@ -375,24 +377,20 @@ const launchAlgo = async (service, account, contract, config) => {
375
377
  algoLogger.error(ui, 'CONNECTION ERROR', e.message.substring(0, 50));
376
378
  }
377
379
 
378
- // Poll account P&L from API
380
+ // Poll account P&L and sync with real trades from API
379
381
  const pollPnL = async () => {
380
382
  try {
383
+ // Get account P&L
381
384
  const accountResult = await service.getTradingAccounts();
382
385
  if (accountResult.success && accountResult.accounts) {
383
386
  const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
384
387
  if (acc && acc.profitAndLoss !== undefined) {
385
388
  if (startingPnL === null) startingPnL = acc.profitAndLoss;
386
389
  stats.pnl = acc.profitAndLoss - startingPnL;
387
-
388
- // Record trade result in strategy
389
- if (stats.pnl !== 0) {
390
- strategy.recordTradeResult(stats.pnl);
391
- }
392
390
  }
393
391
  }
394
392
 
395
- // Check positions
393
+ // Check positions - detect when position closes
396
394
  const posResult = await service.getPositions(account.accountId);
397
395
  if (posResult.success && posResult.positions) {
398
396
  const pos = posResult.positions.find(p => {
@@ -400,24 +398,67 @@ const launchAlgo = async (service, account, contract, config) => {
400
398
  return sym.includes(contract.name) || sym.includes(contractId);
401
399
  });
402
400
 
403
- if (pos && pos.quantity !== 0) {
404
- currentPosition = pos.quantity;
405
- const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
406
- const pnl = pos.profitAndLoss || 0;
401
+ const newPositionQty = pos?.quantity || 0;
402
+
403
+ // Position just closed - cancel remaining orders and log result
404
+ if (lastPositionQty !== 0 && newPositionQty === 0) {
405
+ // Cancel all open orders to prevent new positions
406
+ try {
407
+ await service.cancelAllOrders(account.accountId);
408
+ algoLogger.info(ui, 'ORDERS CANCELLED', 'Position closed - brackets removed');
409
+ } catch (e) {
410
+ // Silent fail
411
+ }
407
412
 
408
- // Check if position closed (win/loss)
409
- if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
410
- else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
411
- } else {
412
- currentPosition = 0;
413
+ // Get real trade data from API
414
+ try {
415
+ const tradesResult = await service.getTrades(account.accountId);
416
+ if (tradesResult.success && tradesResult.trades?.length > 0) {
417
+ // Count completed trades (those with profitAndLoss not null)
418
+ const completedTrades = tradesResult.trades.filter(t => t.profitAndLoss !== null);
419
+
420
+ // Update stats from real trades
421
+ let wins = 0, losses = 0;
422
+ for (const trade of completedTrades) {
423
+ if (trade.profitAndLoss > 0) wins++;
424
+ else if (trade.profitAndLoss < 0) losses++;
425
+ }
426
+ stats.trades = completedTrades.length;
427
+ stats.wins = wins;
428
+ stats.losses = losses;
429
+
430
+ // Log the trade that just closed
431
+ const lastTrade = completedTrades[completedTrades.length - 1];
432
+ if (lastTrade) {
433
+ const pnl = lastTrade.profitAndLoss || 0;
434
+ const side = lastTrade.side === 0 ? 'LONG' : 'SHORT';
435
+ const exitPrice = lastTrade.price || 0;
436
+
437
+ if (pnl >= 0) {
438
+ algoLogger.targetHit(ui, symbolName, exitPrice, pnl);
439
+ } else {
440
+ algoLogger.stopHit(ui, symbolName, exitPrice, Math.abs(pnl));
441
+ }
442
+ algoLogger.positionClosed(ui, symbolName, side, contracts, exitPrice, pnl);
443
+
444
+ // Record in strategy for adaptation
445
+ strategy.recordTradeResult(pnl);
446
+ }
447
+ }
448
+ } catch (e) {
449
+ // Silent fail - trades API might not be available
450
+ }
413
451
  }
452
+
453
+ lastPositionQty = newPositionQty;
454
+ currentPosition = newPositionQty;
414
455
  }
415
456
 
416
457
  // Check target/risk
417
458
  if (stats.pnl >= dailyTarget) {
418
459
  stopReason = 'target';
419
460
  running = false;
420
- algoLogger.targetHit(ui, symbolName, 0, stats.pnl);
461
+ algoLogger.info(ui, 'DAILY TARGET REACHED', `+$${stats.pnl.toFixed(2)}`);
421
462
  } else if (stats.pnl <= -maxRisk) {
422
463
  stopReason = 'risk';
423
464
  running = false;
@@ -186,8 +186,8 @@ const showStats = async (service) => {
186
186
  } catch (e) {}
187
187
  }
188
188
 
189
- // ========== AGGREGATE STATS FROM API DATA ==========
190
- // Stats come from API (lifetimeStats) or calculated from API trade data
189
+ // ========== AGGREGATE STATS FROM TRADE HISTORY (API DATA) ==========
190
+ // Calculate stats from actual trades to ensure consistency with displayed trades
191
191
 
192
192
  let stats = {
193
193
  totalTrades: 0, winningTrades: 0, losingTrades: 0,
@@ -197,27 +197,8 @@ const showStats = async (service) => {
197
197
  longTrades: 0, shortTrades: 0, longWins: 0, shortWins: 0
198
198
  };
199
199
 
200
- // First: aggregate lifetimeStats from APIs (ProjectX)
201
- for (const account of activeAccounts) {
202
- if (account.lifetimeStats) {
203
- const s = account.lifetimeStats;
204
- stats.totalTrades += s.totalTrades || 0;
205
- stats.winningTrades += s.winningTrades || 0;
206
- stats.losingTrades += s.losingTrades || 0;
207
- stats.totalWinAmount += s.totalWinAmount || 0;
208
- stats.totalLossAmount += s.totalLossAmount || 0;
209
- stats.bestTrade = Math.max(stats.bestTrade, s.bestTrade || 0);
210
- stats.worstTrade = Math.min(stats.worstTrade, s.worstTrade || 0);
211
- stats.totalVolume += s.totalVolume || 0;
212
- stats.maxConsecutiveWins = Math.max(stats.maxConsecutiveWins, s.maxConsecutiveWins || 0);
213
- stats.maxConsecutiveLosses = Math.max(stats.maxConsecutiveLosses, s.maxConsecutiveLosses || 0);
214
- stats.longTrades += s.longTrades || 0;
215
- stats.shortTrades += s.shortTrades || 0;
216
- }
217
- }
218
-
219
- // If no lifetimeStats, calculate from trade history (still 100% API data)
220
- if (stats.totalTrades === 0 && allTrades.length > 0) {
200
+ // Calculate stats from trade history (100% API data, consistent with displayed trades)
201
+ if (allTrades.length > 0) {
221
202
  stats.totalTrades = allTrades.length;
222
203
  let consecutiveWins = 0, consecutiveLosses = 0;
223
204
 
@@ -464,11 +445,16 @@ const showStats = async (service) => {
464
445
  // Header
465
446
  const header = ` ${'Time'.padEnd(colTime)}| ${'Symbol'.padEnd(colSymbol)}| ${'Price'.padEnd(colPrice)}| ${'P&L'.padEnd(colPnl)}| ${'Side'.padEnd(colSide)}| ${'Account'.padEnd(colAccount - 2)}`;
466
447
  console.log(chalk.cyan('\u2551') + chalk.white(header) + chalk.cyan('\u2551'));
467
- console.log(chalk.cyan('\u2551') + chalk.gray('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2551'));
448
+ console.log(chalk.cyan('\u255F') + chalk.cyan('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2562'));
468
449
 
469
- const recentTrades = allTrades.slice(-10).reverse();
450
+ // Show ALL trades, sorted by time (most recent first)
451
+ const sortedTrades = [...allTrades].sort((a, b) => {
452
+ const timeA = new Date(a.creationTimestamp || a.timestamp || 0).getTime();
453
+ const timeB = new Date(b.creationTimestamp || b.timestamp || 0).getTime();
454
+ return timeB - timeA;
455
+ });
470
456
 
471
- for (const trade of recentTrades) {
457
+ for (const trade of sortedTrades) {
472
458
  const timestamp = trade.creationTimestamp || trade.timestamp;
473
459
  const time = timestamp ? new Date(timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) : '--:--';
474
460
  const symbol = extractSymbol(trade.contractId || trade.symbol);
@@ -493,11 +479,6 @@ const showStats = async (service) => {
493
479
  const row = ` ${timeStr}| ${symbolStr}| ${priceStr}| ${pnlColored}| ${sideColored}| ${accountStr}`;
494
480
  console.log(chalk.cyan('\u2551') + row + chalk.cyan('\u2551'));
495
481
  }
496
-
497
- if (allTrades.length > 10) {
498
- const moreMsg = ` ... and ${allTrades.length - 10} more trades`;
499
- console.log(chalk.cyan('\u2551') + moreMsg.padEnd(innerWidth) + chalk.cyan('\u2551'));
500
- }
501
482
  } else {
502
483
  const msg = connectionTypes.rithmic > 0
503
484
  ? ' No trade history (Rithmic API limitation)'
@@ -384,6 +384,42 @@ class ProjectXService {
384
384
  }
385
385
  }
386
386
 
387
+ /**
388
+ * Get trades history for today
389
+ * @param {number|string} accountId - Account ID
390
+ * @returns {Promise<{success: boolean, trades: Array, error?: string}>}
391
+ */
392
+ async getTrades(accountId) {
393
+ try {
394
+ const id = validateAccountId(accountId);
395
+
396
+ // Get today's trades (from midnight UTC)
397
+ const now = new Date();
398
+ const startOfDay = new Date(now);
399
+ startOfDay.setUTCHours(0, 0, 0, 0);
400
+
401
+ const response = await this._request(
402
+ this.propfirm.gatewayApi,
403
+ '/api/Trade/search',
404
+ 'POST',
405
+ {
406
+ accountId: id,
407
+ startTimestamp: startOfDay.toISOString(),
408
+ endTimestamp: now.toISOString()
409
+ }
410
+ );
411
+
412
+ if (response.statusCode === 200) {
413
+ const trades = response.data.trades || response.data || [];
414
+ return { success: true, trades: Array.isArray(trades) ? trades : [] };
415
+ }
416
+
417
+ return { success: true, trades: [] };
418
+ } catch (err) {
419
+ return { success: true, trades: [], error: err.message };
420
+ }
421
+ }
422
+
387
423
  /**
388
424
  * Place an order
389
425
  * @param {Object} orderData - Order details