hedgequantx 2.6.44 → 2.6.46

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.6.44",
3
+ "version": "2.6.46",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -245,14 +245,21 @@ const launchAlgo = async (service, account, contract, config) => {
245
245
  risk: maxRisk,
246
246
  propfirm: account.propfirm || 'Unknown',
247
247
  platform: connectionType,
248
- pnl: 0,
249
- // R Trader style P&L breakdown
250
- openPnl: 0, // Unrealized P&L (current position)
251
- closedPnl: 0, // Realized P&L (closed trades today)
252
- // Position info (like R Trader)
253
- position: 0, // Current position qty (+ long, - short)
254
- entryPrice: 0, // Average entry price
255
- lastPrice: 0, // Last market price
248
+ // ═══════════════════════════════════════════════════════════════════════════
249
+ // R TRADER METRICS - All from Rithmic API (ACCOUNT_PNL_UPDATE 451)
250
+ // ═══════════════════════════════════════════════════════════════════════════
251
+ pnl: null, // Today's P&L (openPnl + closedPnl)
252
+ openPnl: null, // Unrealized P&L (current position) - from INSTRUMENT_PNL_UPDATE 450
253
+ closedPnl: null, // Realized P&L (closed trades today)
254
+ balance: null, // Account Balance
255
+ buyingPower: null, // Available Buying Power
256
+ margin: null, // Margin Balance
257
+ netLiquidation: null, // Net Liquidation Value (balance + openPnl)
258
+ // Position info (like R Trader Positions panel) - from INSTRUMENT_PNL_UPDATE 450
259
+ position: 0, // Current position qty (+ long, - short)
260
+ entryPrice: 0, // Average entry price
261
+ lastPrice: 0, // Last market price (from ticker)
262
+ // Trading stats
256
263
  trades: 0,
257
264
  wins: 0,
258
265
  losses: 0,
@@ -379,17 +386,45 @@ const launchAlgo = async (service, account, contract, config) => {
379
386
  // Only update for our account
380
387
  if (pnlData.accountId !== rithmicAccountId) return;
381
388
 
382
- // R Trader style P&L breakdown
383
- const openPnl = parseFloat(pnlData.openPositionPnl || 0);
384
- const closedPnl = parseFloat(pnlData.closedPositionPnl || 0);
389
+ // ═══════════════════════════════════════════════════════════════════════════
390
+ // ACCOUNT_PNL_UPDATE (451) - All R Trader account-level metrics
391
+ // ═══════════════════════════════════════════════════════════════════════════
385
392
 
386
- // Update stats (like R Trader display)
387
- stats.openPnl = openPnl; // Unrealized
388
- stats.closedPnl = closedPnl; // Realized
389
- stats.pnl = openPnl + closedPnl; // Total (same as R Trader "Today's P&L")
393
+ // Closed P&L (realized) - from closed trades today
394
+ if (pnlData.closedPositionPnl !== undefined) {
395
+ stats.closedPnl = parseFloat(pnlData.closedPositionPnl);
396
+ }
397
+
398
+ // Account Balance
399
+ if (pnlData.accountBalance !== undefined) {
400
+ stats.balance = parseFloat(pnlData.accountBalance);
401
+ }
402
+
403
+ // Buying Power (Available)
404
+ if (pnlData.availableBuyingPower !== undefined) {
405
+ stats.buyingPower = parseFloat(pnlData.availableBuyingPower);
406
+ }
407
+
408
+ // Margin Balance
409
+ if (pnlData.marginBalance !== undefined) {
410
+ stats.margin = parseFloat(pnlData.marginBalance);
411
+ }
412
+
413
+ // Net Liquidation Value (balance + openPnl) - same as R Trader
414
+ if (pnlData.netLiquidation !== undefined) {
415
+ stats.netLiquidation = parseFloat(pnlData.netLiquidation);
416
+ } else if (stats.balance !== null) {
417
+ // Calculate if not provided directly
418
+ stats.netLiquidation = stats.balance + (stats.openPnl || 0);
419
+ }
420
+
421
+ // Total P&L = openPnl (from positionUpdate) + closedPnl (from pnlUpdate)
422
+ // This matches R Trader's "Today's P&L" calculation
423
+ stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
390
424
  });
391
425
 
392
426
  // Listen to position updates for real-time position tracking (like R Trader)
427
+ // INSTRUMENT_PNL_UPDATE (450) provides per-instrument P&L in real-time
393
428
  service.on('positionUpdate', (pos) => {
394
429
  if (!pos || pos.accountId !== rithmicAccountId) return;
395
430
  if (pos.symbol !== symbolName && !pos.symbol?.includes(symbolName.replace(/[A-Z]\d+$/, ''))) return;
@@ -398,6 +433,17 @@ const launchAlgo = async (service, account, contract, config) => {
398
433
  stats.position = pos.quantity || 0;
399
434
  stats.entryPrice = pos.averagePrice || 0;
400
435
  currentPosition = pos.quantity || 0;
436
+
437
+ // CRITICAL: Update Open P&L from instrument-level data (real-time, same as R Trader)
438
+ // pos.openPnl comes from INSTRUMENT_PNL_UPDATE (450) - this is the unrealized P&L
439
+ if (pos.openPnl !== undefined && pos.openPnl !== null) {
440
+ stats.openPnl = pos.openPnl;
441
+ }
442
+ // Update day P&L if available
443
+ if (pos.dayPnl !== undefined && pos.dayPnl !== null) {
444
+ // Total P&L = openPnl + closedPnl (same formula as R Trader)
445
+ stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
446
+ }
401
447
  });
402
448
 
403
449
  // Initialize AI Strategy Supervisor - agents observe, learn & optimize
@@ -210,29 +210,55 @@ class AlgoUI {
210
210
 
211
211
  this._line(chalk.cyan(GM));
212
212
 
213
- // Row 4: Target | Risk
213
+ // Row 4: Balance | Net Liquidation (R Trader style)
214
+ const balanceStr = stats.balance !== null && stats.balance !== undefined
215
+ ? '$' + stats.balance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
216
+ : '--';
217
+ const netLiqStr = stats.netLiquidation !== null && stats.netLiquidation !== undefined
218
+ ? '$' + stats.netLiquidation.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
219
+ : '--';
220
+ const r4c1 = buildCell('BALANCE', balanceStr, chalk.white, colL);
221
+ const r4c2 = buildCell('NET LIQ', netLiqStr, chalk.cyan, colR);
222
+ row(r4c1.padded, r4c2.padded);
223
+
224
+ this._line(chalk.cyan(GM));
225
+
226
+ // Row 5: Buying Power | Margin (R Trader style)
227
+ const bpStr = stats.buyingPower !== null && stats.buyingPower !== undefined
228
+ ? '$' + stats.buyingPower.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
229
+ : '--';
230
+ const marginStr = stats.margin !== null && stats.margin !== undefined
231
+ ? '$' + stats.margin.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
232
+ : '--';
233
+ const r5c1 = buildCell('BUYING PWR', bpStr, chalk.green, colL);
234
+ const r5c2 = buildCell('MARGIN', marginStr, chalk.yellow, colR);
235
+ row(r5c1.padded, r5c2.padded);
236
+
237
+ this._line(chalk.cyan(GM));
238
+
239
+ // Row 6: Target | Risk
214
240
  const targetStr = stats.target !== null && stats.target !== undefined ? '$' + stats.target.toFixed(2) : '--';
215
241
  const riskStr = stats.risk !== null && stats.risk !== undefined ? '$' + stats.risk.toFixed(2) : '--';
216
- const r4c1 = buildCell('TARGET', targetStr, chalk.green, colL);
217
- const r4c2 = buildCell('RISK', riskStr, chalk.red, colR);
218
- row(r4c1.padded, r4c2.padded);
242
+ const r6c1 = buildCell('TARGET', targetStr, chalk.green, colL);
243
+ const r6c2 = buildCell('RISK', riskStr, chalk.red, colR);
244
+ row(r6c1.padded, r6c2.padded);
219
245
 
220
246
  this._line(chalk.cyan(GM));
221
247
 
222
- // Row 5: Trades W/L | Entry Price (if in position)
223
- const r5c1t = ` ${chalk.bold('TRADES')}: ${chalk.cyan.bold(stats.trades || 0)} ${chalk.bold('W/L')}: ${chalk.green.bold(stats.wins || 0)}/${chalk.red.bold(stats.losses || 0)}`;
224
- const r5c1p = ` TRADES: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
248
+ // Row 7: Trades W/L | Entry Price (if in position)
249
+ const r7c1t = ` ${chalk.bold('TRADES')}: ${chalk.cyan.bold(stats.trades || 0)} ${chalk.bold('W/L')}: ${chalk.green.bold(stats.wins || 0)}/${chalk.red.bold(stats.losses || 0)}`;
250
+ const r7c1p = ` TRADES: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
225
251
  const entryStr = stats.entryPrice > 0 ? stats.entryPrice.toFixed(2) : '--';
226
- const r5c2 = buildCell('ENTRY', entryStr, chalk.yellow, colR);
227
- row(r5c1t + pad(colL - r5c1p.length), r5c2.padded);
252
+ const r7c2 = buildCell('ENTRY', entryStr, chalk.yellow, colR);
253
+ row(r7c1t + pad(colL - r7c1p.length), r7c2.padded);
228
254
 
229
255
  this._line(chalk.cyan(GM));
230
256
 
231
- // Row 6: Connection | Propfirm
257
+ // Row 8: Connection | Propfirm
232
258
  const connection = stats.platform || 'ProjectX';
233
- const r6c1 = buildCell('CONNECTION', connection, chalk.cyan, colL);
234
- const r6c2 = buildCell('PROPFIRM', stats.propfirm || 'N/A', chalk.cyan, colR);
235
- row(r6c1.padded, r6c2.padded);
259
+ const r8c1 = buildCell('CONNECTION', connection, chalk.cyan, colL);
260
+ const r8c2 = buildCell('PROPFIRM', stats.propfirm || 'N/A', chalk.cyan, colR);
261
+ row(r8c1.padded, r8c2.padded);
236
262
 
237
263
  this._line(chalk.cyan(GB));
238
264
  }
@@ -109,28 +109,46 @@ const getTradingAccounts = async (service) => {
109
109
  debug(`Account ${acc.accountId} pnlData:`, JSON.stringify(pnlData));
110
110
  debug(` accountPnL map size:`, service.accountPnL.size);
111
111
 
112
- // REAL DATA FROM RITHMIC ONLY - NO DEFAULTS
113
- const accountBalance = pnlData.accountBalance ? parseFloat(pnlData.accountBalance) : null;
114
- const openPnL = pnlData.openPositionPnl ? parseFloat(pnlData.openPositionPnl) : null;
115
- const closedPnL = pnlData.closedPositionPnl ? parseFloat(pnlData.closedPositionPnl) : null;
116
- const dayPnL = pnlData.dayPnl ? parseFloat(pnlData.dayPnl) : null;
112
+ // REAL DATA FROM RITHMIC ONLY - NO DEFAULTS (RULES.md compliant)
113
+ // All values are null if not available from API
114
+ const accountBalance = pnlData.accountBalance !== undefined ? parseFloat(pnlData.accountBalance) : null;
115
+ const openPnL = pnlData.openPositionPnl !== undefined ? parseFloat(pnlData.openPositionPnl) : null;
116
+ const closedPnL = pnlData.closedPositionPnl !== undefined ? parseFloat(pnlData.closedPositionPnl) : null;
117
+ const dayPnL = pnlData.dayPnl !== undefined ? parseFloat(pnlData.dayPnl) : null;
118
+
119
+ // R Trader additional metrics (from ACCOUNT_PNL_UPDATE 451)
120
+ const buyingPower = pnlData.availableBuyingPower !== undefined ? parseFloat(pnlData.availableBuyingPower) : null;
121
+ const margin = pnlData.marginBalance !== undefined ? parseFloat(pnlData.marginBalance) : null;
122
+ const cashOnHand = pnlData.cashOnHand !== undefined ? parseFloat(pnlData.cashOnHand) : null;
117
123
 
118
124
  // Calculate P&L like R Trader: openPositionPnl + closedPositionPnl
119
125
  // This matches what R Trader shows as "Today's P&L"
120
126
  const totalPnL = (openPnL !== null || closedPnL !== null)
121
127
  ? (openPnL || 0) + (closedPnL || 0)
122
128
  : null;
129
+
130
+ // Net Liquidation Value = Account Balance + Open P&L (same as R Trader)
131
+ const netLiquidation = (accountBalance !== null || openPnL !== null)
132
+ ? (accountBalance || 0) + (openPnL || 0)
133
+ : null;
123
134
 
124
135
  return {
125
136
  accountId: hashAccountId(acc.accountId),
126
137
  rithmicAccountId: acc.accountId,
127
138
  accountName: acc.accountId, // Never expose real name - only account ID
128
139
  name: acc.accountId, // Never expose real name - only account ID
140
+ // Core metrics (same as R Trader)
129
141
  balance: accountBalance,
130
142
  profitAndLoss: totalPnL, // Same as R Trader: open + closed
131
143
  openPnL: openPnL,
132
144
  closedPnL: closedPnL,
133
- dayPnL: dayPnL, // Keep for reference
145
+ dayPnL: dayPnL,
146
+ // R Trader additional metrics
147
+ buyingPower: buyingPower,
148
+ margin: margin,
149
+ cashOnHand: cashOnHand,
150
+ netLiquidation: netLiquidation,
151
+ // Meta
134
152
  status: 0,
135
153
  platform: 'Rithmic',
136
154
  propfirm: service.propfirm.name,
@@ -332,17 +332,23 @@ const handleAccountPnLUpdate = (service, data) => {
332
332
  debug('Decoded Account PNL:', JSON.stringify(pnl));
333
333
 
334
334
  if (pnl.accountId) {
335
+ // Store ALL R Trader metrics from ACCOUNT_PNL_UPDATE (451)
335
336
  const pnlData = {
337
+ // Core P&L
336
338
  accountBalance: parseFloat(pnl.accountBalance || 0),
337
- cashOnHand: parseFloat(pnl.cashOnHand || 0),
338
- marginBalance: parseFloat(pnl.marginBalance || 0),
339
339
  openPositionPnl: parseFloat(pnl.openPositionPnl || 0),
340
340
  closedPositionPnl: parseFloat(pnl.closedPositionPnl || 0),
341
341
  dayPnl: parseFloat(pnl.dayPnl || 0),
342
+ // R Trader additional metrics
343
+ availableBuyingPower: parseFloat(pnl.availableBuyingPower || 0),
344
+ marginBalance: parseFloat(pnl.marginBalance || 0),
345
+ cashOnHand: parseFloat(pnl.cashOnHand || 0),
346
+ // Net Liquidation = Account Balance + Open P&L (same as R Trader)
347
+ netLiquidation: parseFloat(pnl.accountBalance || 0) + parseFloat(pnl.openPositionPnl || 0),
342
348
  };
343
349
  debug('Storing PNL for account:', pnl.accountId, pnlData);
344
350
  service.accountPnL.set(pnl.accountId, pnlData);
345
- service.emit('pnlUpdate', pnl);
351
+ service.emit('pnlUpdate', { accountId: pnl.accountId, ...pnlData });
346
352
  } else {
347
353
  debug('No accountId in PNL response');
348
354
  }