hedgequantx 2.4.32 → 2.4.34
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 +1 -1
- package/src/pages/algo/one-account.js +58 -17
- package/src/pages/stats.js +26 -32
- package/src/services/projectx/index.js +36 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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.
|
|
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;
|
package/src/pages/stats.js
CHANGED
|
@@ -186,8 +186,8 @@ const showStats = async (service) => {
|
|
|
186
186
|
} catch (e) {}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
// ========== AGGREGATE STATS FROM API DATA ==========
|
|
190
|
-
//
|
|
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
|
-
//
|
|
201
|
-
|
|
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,18 +445,31 @@ 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('\
|
|
448
|
+
console.log(chalk.cyan('\u255F') + chalk.cyan('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2562'));
|
|
449
|
+
|
|
450
|
+
// Show only COMPLETED trades (with P&L), sorted by time (most recent first)
|
|
451
|
+
// Filter out entry fills (P&L = 0 or null) - only show exit fills with real P&L
|
|
452
|
+
const completedTrades = allTrades.filter(t => {
|
|
453
|
+
const pnl = t.profitAndLoss || t.pnl;
|
|
454
|
+
return pnl !== null && pnl !== undefined && pnl !== 0;
|
|
455
|
+
});
|
|
468
456
|
|
|
469
|
-
const
|
|
457
|
+
const sortedTrades = [...completedTrades].sort((a, b) => {
|
|
458
|
+
const timeA = new Date(a.creationTimestamp || a.timestamp || 0).getTime();
|
|
459
|
+
const timeB = new Date(b.creationTimestamp || b.timestamp || 0).getTime();
|
|
460
|
+
return timeB - timeA;
|
|
461
|
+
});
|
|
470
462
|
|
|
471
|
-
for (const trade of
|
|
463
|
+
for (const trade of sortedTrades) {
|
|
472
464
|
const timestamp = trade.creationTimestamp || trade.timestamp;
|
|
473
465
|
const time = timestamp ? new Date(timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) : '--:--';
|
|
474
466
|
const symbol = extractSymbol(trade.contractId || trade.symbol);
|
|
475
467
|
const price = (trade.price || 0).toFixed(2);
|
|
476
468
|
const pnl = trade.profitAndLoss || trade.pnl || 0;
|
|
477
469
|
const pnlText = pnl >= 0 ? `+$${pnl.toFixed(0)}` : `-$${Math.abs(pnl).toFixed(0)}`;
|
|
478
|
-
|
|
470
|
+
// For completed trades, show the original direction (opposite of exit side)
|
|
471
|
+
const exitSide = trade.side; // 0=BUY exit means was SHORT, 1=SELL exit means was LONG
|
|
472
|
+
const tradeSide = exitSide === 0 ? 'SHORT' : 'LONG';
|
|
479
473
|
const accountName = (trade.accountName || 'N/A').substring(0, colAccount - 3);
|
|
480
474
|
|
|
481
475
|
// Build row with exact widths
|
|
@@ -483,20 +477,20 @@ const showStats = async (service) => {
|
|
|
483
477
|
const symbolStr = symbol.padEnd(colSymbol);
|
|
484
478
|
const priceStr = price.padEnd(colPrice);
|
|
485
479
|
const pnlStr = pnlText.padEnd(colPnl);
|
|
486
|
-
const sideStr =
|
|
480
|
+
const sideStr = tradeSide.padEnd(colSide);
|
|
487
481
|
const accountStr = accountName.padEnd(colAccount - 2);
|
|
488
482
|
|
|
489
483
|
// Colored versions
|
|
490
484
|
const pnlColored = pnl >= 0 ? chalk.green(pnlStr) : chalk.red(pnlStr);
|
|
491
|
-
const sideColored =
|
|
485
|
+
const sideColored = tradeSide === 'LONG' ? chalk.green(sideStr) : chalk.red(sideStr);
|
|
492
486
|
|
|
493
487
|
const row = ` ${timeStr}| ${symbolStr}| ${priceStr}| ${pnlColored}| ${sideColored}| ${accountStr}`;
|
|
494
488
|
console.log(chalk.cyan('\u2551') + row + chalk.cyan('\u2551'));
|
|
495
489
|
}
|
|
496
490
|
|
|
497
|
-
if (
|
|
498
|
-
const
|
|
499
|
-
console.log(chalk.cyan('\u2551') +
|
|
491
|
+
if (sortedTrades.length === 0) {
|
|
492
|
+
const msg = ' No completed trades yet';
|
|
493
|
+
console.log(chalk.cyan('\u2551') + chalk.gray(msg.padEnd(innerWidth)) + chalk.cyan('\u2551'));
|
|
500
494
|
}
|
|
501
495
|
} else {
|
|
502
496
|
const msg = connectionTypes.rithmic > 0
|
|
@@ -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
|