hedgequantx 2.4.31 → 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.31",
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;
@@ -301,16 +303,23 @@ const launchAlgo = async (service, account, contract, config) => {
301
303
  tps++;
302
304
  const latencyStart = Date.now();
303
305
 
306
+ // Debug: log first tick to see structure
307
+ if (tickCount === 1) {
308
+ algoLogger.info(ui, 'FIRST TICK', `price=${tick.price} bid=${tick.bid} ask=${tick.ask} vol=${tick.volume}`);
309
+ }
310
+
304
311
  // Feed tick to strategy
305
- strategy.processTick({
312
+ const tickData = {
306
313
  contractId: tick.contractId || contractId,
307
- price: tick.price,
314
+ price: tick.price || tick.lastPrice || tick.bid,
308
315
  bid: tick.bid,
309
316
  ask: tick.ask,
310
- volume: tick.volume || 1,
311
- side: tick.lastTradeSide || 'unknown',
317
+ volume: tick.volume || tick.size || 1,
318
+ side: tick.lastTradeSide || tick.side || 'unknown',
312
319
  timestamp: tick.timestamp || Date.now()
313
- });
320
+ };
321
+
322
+ strategy.processTick(tickData);
314
323
 
315
324
  stats.latency = Date.now() - latencyStart;
316
325
 
@@ -368,24 +377,20 @@ const launchAlgo = async (service, account, contract, config) => {
368
377
  algoLogger.error(ui, 'CONNECTION ERROR', e.message.substring(0, 50));
369
378
  }
370
379
 
371
- // Poll account P&L from API
380
+ // Poll account P&L and sync with real trades from API
372
381
  const pollPnL = async () => {
373
382
  try {
383
+ // Get account P&L
374
384
  const accountResult = await service.getTradingAccounts();
375
385
  if (accountResult.success && accountResult.accounts) {
376
386
  const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
377
387
  if (acc && acc.profitAndLoss !== undefined) {
378
388
  if (startingPnL === null) startingPnL = acc.profitAndLoss;
379
389
  stats.pnl = acc.profitAndLoss - startingPnL;
380
-
381
- // Record trade result in strategy
382
- if (stats.pnl !== 0) {
383
- strategy.recordTradeResult(stats.pnl);
384
- }
385
390
  }
386
391
  }
387
392
 
388
- // Check positions
393
+ // Check positions - detect when position closes
389
394
  const posResult = await service.getPositions(account.accountId);
390
395
  if (posResult.success && posResult.positions) {
391
396
  const pos = posResult.positions.find(p => {
@@ -393,24 +398,67 @@ const launchAlgo = async (service, account, contract, config) => {
393
398
  return sym.includes(contract.name) || sym.includes(contractId);
394
399
  });
395
400
 
396
- if (pos && pos.quantity !== 0) {
397
- currentPosition = pos.quantity;
398
- const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
399
- 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
+ }
400
412
 
401
- // Check if position closed (win/loss)
402
- if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
403
- else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
404
- } else {
405
- 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
+ }
406
451
  }
452
+
453
+ lastPositionQty = newPositionQty;
454
+ currentPosition = newPositionQty;
407
455
  }
408
456
 
409
457
  // Check target/risk
410
458
  if (stats.pnl >= dailyTarget) {
411
459
  stopReason = 'target';
412
460
  running = false;
413
- algoLogger.targetHit(ui, symbolName, 0, stats.pnl);
461
+ algoLogger.info(ui, 'DAILY TARGET REACHED', `+$${stats.pnl.toFixed(2)}`);
414
462
  } else if (stats.pnl <= -maxRisk) {
415
463
  stopReason = 'risk';
416
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