hedgequantx 2.6.45 → 2.6.47
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
|
@@ -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
|
-
|
|
249
|
-
// R
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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,12 +386,37 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
379
386
|
// Only update for our account
|
|
380
387
|
if (pnlData.accountId !== rithmicAccountId) return;
|
|
381
388
|
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
|
|
389
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
390
|
+
// ACCOUNT_PNL_UPDATE (451) - All R Trader account-level metrics
|
|
391
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
385
392
|
|
|
386
|
-
//
|
|
387
|
-
|
|
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
|
+
}
|
|
388
420
|
|
|
389
421
|
// Total P&L = openPnl (from positionUpdate) + closedPnl (from pnlUpdate)
|
|
390
422
|
// This matches R Trader's "Today's P&L" calculation
|
|
@@ -406,11 +438,13 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
406
438
|
// pos.openPnl comes from INSTRUMENT_PNL_UPDATE (450) - this is the unrealized P&L
|
|
407
439
|
if (pos.openPnl !== undefined && pos.openPnl !== null) {
|
|
408
440
|
stats.openPnl = pos.openPnl;
|
|
409
|
-
|
|
410
|
-
// Update day P&L if available
|
|
411
|
-
if (pos.dayPnl !== undefined && pos.dayPnl !== null) {
|
|
441
|
+
// Recalculate total P&L whenever Open P&L changes
|
|
412
442
|
// Total P&L = openPnl + closedPnl (same formula as R Trader)
|
|
413
443
|
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
444
|
+
// Also update Net Liquidation (balance + openPnl)
|
|
445
|
+
if (stats.balance !== null) {
|
|
446
|
+
stats.netLiquidation = stats.balance + stats.openPnl;
|
|
447
|
+
}
|
|
414
448
|
}
|
|
415
449
|
});
|
|
416
450
|
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -210,29 +210,55 @@ class AlgoUI {
|
|
|
210
210
|
|
|
211
211
|
this._line(chalk.cyan(GM));
|
|
212
212
|
|
|
213
|
-
// Row 4:
|
|
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
|
|
217
|
-
const
|
|
218
|
-
row(
|
|
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
|
|
223
|
-
const
|
|
224
|
-
const
|
|
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
|
|
227
|
-
row(
|
|
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
|
|
257
|
+
// Row 8: Connection | Propfirm
|
|
232
258
|
const connection = stats.platform || 'ProjectX';
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
row(
|
|
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
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
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,
|
|
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
|
}
|
|
@@ -353,6 +359,8 @@ const handleAccountPnLUpdate = (service, data) => {
|
|
|
353
359
|
|
|
354
360
|
/**
|
|
355
361
|
* Handle instrument PnL update (positions)
|
|
362
|
+
* INSTRUMENT_PNL_UPDATE (450) - Real-time position and P&L per instrument
|
|
363
|
+
* This is the PRIMARY source for unrealized P&L (same as R Trader Positions panel)
|
|
356
364
|
*/
|
|
357
365
|
const handleInstrumentPnLUpdate = (service, data) => {
|
|
358
366
|
try {
|
|
@@ -361,26 +369,33 @@ const handleInstrumentPnLUpdate = (service, data) => {
|
|
|
361
369
|
const key = `${pos.accountId}:${pos.symbol}:${pos.exchange}`;
|
|
362
370
|
const netQty = pos.netQuantity || pos.openPositionQuantity || ((pos.buyQty || 0) - (pos.sellQty || 0));
|
|
363
371
|
|
|
372
|
+
// Build position data - ALWAYS emit, even when FLAT (netQty === 0)
|
|
373
|
+
// This ensures Open P&L resets to 0 when position closes (like R Trader)
|
|
374
|
+
const positionData = {
|
|
375
|
+
accountId: pos.accountId,
|
|
376
|
+
symbol: pos.symbol,
|
|
377
|
+
exchange: pos.exchange || 'CME',
|
|
378
|
+
quantity: netQty,
|
|
379
|
+
averagePrice: netQty !== 0 ? (pos.avgOpenFillPrice || 0) : 0,
|
|
380
|
+
// Open P&L from API - this is the real-time unrealized P&L (same as R Trader)
|
|
381
|
+
openPnl: parseFloat(pos.openPositionPnl || pos.dayOpenPnl || 0),
|
|
382
|
+
closedPnl: parseFloat(pos.closedPositionPnl || pos.dayClosedPnl || 0),
|
|
383
|
+
dayPnl: parseFloat(pos.dayPnl || 0),
|
|
384
|
+
isSnapshot: pos.isSnapshot || false,
|
|
385
|
+
};
|
|
386
|
+
|
|
364
387
|
if (netQty !== 0) {
|
|
365
|
-
service.positions.set(key,
|
|
366
|
-
accountId: pos.accountId,
|
|
367
|
-
symbol: pos.symbol,
|
|
368
|
-
exchange: pos.exchange || 'CME',
|
|
369
|
-
quantity: netQty,
|
|
370
|
-
averagePrice: pos.avgOpenFillPrice || 0,
|
|
371
|
-
openPnl: parseFloat(pos.openPositionPnl || pos.dayOpenPnl || 0),
|
|
372
|
-
closedPnl: parseFloat(pos.closedPositionPnl || pos.dayClosedPnl || 0),
|
|
373
|
-
dayPnl: parseFloat(pos.dayPnl || 0),
|
|
374
|
-
isSnapshot: pos.isSnapshot || false,
|
|
375
|
-
});
|
|
388
|
+
service.positions.set(key, positionData);
|
|
376
389
|
} else {
|
|
377
390
|
service.positions.delete(key);
|
|
378
391
|
}
|
|
379
392
|
|
|
380
|
-
|
|
393
|
+
// ALWAYS emit positionUpdate - even when FLAT
|
|
394
|
+
// This ensures UI updates Open P&L to 0 when position closes
|
|
395
|
+
service.emit('positionUpdate', positionData);
|
|
381
396
|
}
|
|
382
397
|
} catch (e) {
|
|
383
|
-
|
|
398
|
+
debug('Error decoding Instrument PNL:', e.message);
|
|
384
399
|
}
|
|
385
400
|
};
|
|
386
401
|
|