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 +1 -1
- package/src/pages/algo/one-account.js +58 -17
- package/src/pages/stats.js +12 -31
- 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,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('\
|
|
448
|
+
console.log(chalk.cyan('\u255F') + chalk.cyan('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2562'));
|
|
468
449
|
|
|
469
|
-
|
|
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
|
|
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
|