hedgequantx 2.6.128 → 2.6.129
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 +214 -27
package/package.json
CHANGED
|
@@ -1385,6 +1385,8 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
1385
1385
|
* Uses single market feed connection with multiple subscriptions
|
|
1386
1386
|
* Each symbol has its own PositionManager and strategy instance
|
|
1387
1387
|
*
|
|
1388
|
+
* Same logic as launchAlgo but for multiple symbols
|
|
1389
|
+
*
|
|
1388
1390
|
* @param {Object} service - Rithmic trading service
|
|
1389
1391
|
* @param {Object} account - Trading account
|
|
1390
1392
|
* @param {Array} contracts - Array of contracts to trade
|
|
@@ -1407,7 +1409,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1407
1409
|
mode: 'one-account'
|
|
1408
1410
|
});
|
|
1409
1411
|
|
|
1410
|
-
// Shared stats
|
|
1412
|
+
// Shared stats (same structure as launchAlgo)
|
|
1411
1413
|
const stats = {
|
|
1412
1414
|
accountName,
|
|
1413
1415
|
symbol: symbolsDisplay,
|
|
@@ -1420,7 +1422,12 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1420
1422
|
openPnl: null,
|
|
1421
1423
|
closedPnl: null,
|
|
1422
1424
|
balance: null,
|
|
1425
|
+
buyingPower: null,
|
|
1426
|
+
margin: null,
|
|
1427
|
+
netLiquidation: null,
|
|
1423
1428
|
position: 0,
|
|
1429
|
+
entryPrice: 0,
|
|
1430
|
+
lastPrice: 0,
|
|
1424
1431
|
trades: 0,
|
|
1425
1432
|
wins: 0,
|
|
1426
1433
|
losses: 0,
|
|
@@ -1429,7 +1436,12 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1429
1436
|
connected: false,
|
|
1430
1437
|
startTime: Date.now(),
|
|
1431
1438
|
aiSupervision: enableAI || false,
|
|
1439
|
+
aiMode: null,
|
|
1440
|
+
agentCount: 0,
|
|
1432
1441
|
fastPath: true,
|
|
1442
|
+
avgEntryLatency: 0,
|
|
1443
|
+
avgFillLatency: 0,
|
|
1444
|
+
entryLatencies: [],
|
|
1433
1445
|
// Per-symbol tracking
|
|
1434
1446
|
symbolStats: {},
|
|
1435
1447
|
};
|
|
@@ -1437,23 +1449,42 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1437
1449
|
// Initialize per-symbol stats
|
|
1438
1450
|
contracts.forEach(c => {
|
|
1439
1451
|
const name = c.name || c.symbol;
|
|
1440
|
-
stats.symbolStats[name] = {
|
|
1452
|
+
stats.symbolStats[name] = {
|
|
1453
|
+
position: 0,
|
|
1454
|
+
trades: 0,
|
|
1455
|
+
wins: 0,
|
|
1456
|
+
losses: 0,
|
|
1457
|
+
pnl: 0,
|
|
1458
|
+
tickCount: 0,
|
|
1459
|
+
};
|
|
1441
1460
|
});
|
|
1442
1461
|
|
|
1443
1462
|
let running = true;
|
|
1444
1463
|
let stopReason = null;
|
|
1464
|
+
let tickCount = 0;
|
|
1465
|
+
|
|
1466
|
+
// Store contract info for later use
|
|
1467
|
+
const contractInfoMap = {};
|
|
1468
|
+
contracts.forEach(c => {
|
|
1469
|
+
const name = c.name || c.symbol;
|
|
1470
|
+
contractInfoMap[name] = {
|
|
1471
|
+
tickSize: c.tickSize ?? null,
|
|
1472
|
+
tickValue: c.tickValue ?? null,
|
|
1473
|
+
contractId: c.id || c.symbol || c.name,
|
|
1474
|
+
exchange: c.exchange || 'CME',
|
|
1475
|
+
};
|
|
1476
|
+
});
|
|
1445
1477
|
|
|
1446
1478
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1447
|
-
// POSITION MANAGERS - One per symbol
|
|
1479
|
+
// POSITION MANAGERS & STRATEGIES - One per symbol
|
|
1448
1480
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1449
1481
|
const positionManagers = {};
|
|
1450
1482
|
const strategies = {};
|
|
1483
|
+
const pendingOrders = {}; // Track pending orders per symbol
|
|
1451
1484
|
|
|
1452
1485
|
contracts.forEach(contract => {
|
|
1453
1486
|
const symbolName = contract.name || contract.symbol;
|
|
1454
|
-
const
|
|
1455
|
-
const tickSize = contract.tickSize ?? null;
|
|
1456
|
-
const tickValue = contract.tickValue ?? null;
|
|
1487
|
+
const { tickSize, tickValue, contractId } = contractInfoMap[symbolName];
|
|
1457
1488
|
|
|
1458
1489
|
// Create strategy instance for this symbol
|
|
1459
1490
|
const strategy = Object.create(hftStrategy);
|
|
@@ -1461,6 +1492,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1461
1492
|
strategy.initialize(contractId, tickSize, tickValue);
|
|
1462
1493
|
}
|
|
1463
1494
|
strategies[symbolName] = strategy;
|
|
1495
|
+
pendingOrders[symbolName] = false;
|
|
1464
1496
|
|
|
1465
1497
|
// Create position manager for this symbol
|
|
1466
1498
|
const pm = new PositionManager(service, strategy);
|
|
@@ -1470,11 +1502,16 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1470
1502
|
pm.start();
|
|
1471
1503
|
positionManagers[symbolName] = pm;
|
|
1472
1504
|
|
|
1473
|
-
//
|
|
1505
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1506
|
+
// POSITION MANAGER EVENTS (same as launchAlgo)
|
|
1507
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1474
1508
|
pm.on('entryFilled', ({ position, fillLatencyMs }) => {
|
|
1509
|
+
stats.entryLatencies.push(fillLatencyMs);
|
|
1510
|
+
stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
|
|
1475
1511
|
const side = position.side === 0 ? 'LONG' : 'SHORT';
|
|
1476
1512
|
ui.addLog('filled', `[${symbolName}] ${side} ${position.size}x @ ${position.entryPrice} | ${fillLatencyMs}ms`);
|
|
1477
1513
|
stats.symbolStats[symbolName].position = position.side === 0 ? position.size : -position.size;
|
|
1514
|
+
ui.render(stats);
|
|
1478
1515
|
});
|
|
1479
1516
|
|
|
1480
1517
|
pm.on('exitFilled', ({ exitPrice, pnlTicks, holdDurationMs }) => {
|
|
@@ -1486,44 +1523,109 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1486
1523
|
stats.symbolStats[symbolName].trades++;
|
|
1487
1524
|
stats.trades++;
|
|
1488
1525
|
|
|
1526
|
+
// Record trade for Recovery Math
|
|
1527
|
+
recoveryMath.recordTrade({
|
|
1528
|
+
pnl: pnlDollars,
|
|
1529
|
+
ticks: pnlTicks,
|
|
1530
|
+
side: pnlTicks >= 0 ? 'win' : 'loss',
|
|
1531
|
+
duration: holdDurationMs,
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
// Update Recovery Mode state
|
|
1535
|
+
const recovery = recoveryMath.updateSessionPnL(
|
|
1536
|
+
stats.sessionPnl,
|
|
1537
|
+
FAST_SCALPING.RECOVERY?.ACTIVATION_PNL || -300,
|
|
1538
|
+
FAST_SCALPING.RECOVERY?.DEACTIVATION_PNL || -100
|
|
1539
|
+
);
|
|
1540
|
+
|
|
1541
|
+
if (recovery.justActivated) {
|
|
1542
|
+
stats.recoveryMode = true;
|
|
1543
|
+
ui.addLog('warning', `RECOVERY MODE ON - Kelly: ${(recoveryMath.calcKelly() * 100).toFixed(0)}%`);
|
|
1544
|
+
} else if (recovery.justDeactivated) {
|
|
1545
|
+
stats.recoveryMode = false;
|
|
1546
|
+
ui.addLog('success', `RECOVERY MODE OFF - Session P&L: $${stats.sessionPnl.toFixed(2)}`);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1489
1549
|
if (pnlDollars >= 0) {
|
|
1490
1550
|
stats.wins++;
|
|
1491
1551
|
stats.symbolStats[symbolName].wins++;
|
|
1492
|
-
ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} | ${holdSec}s`);
|
|
1552
|
+
ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
1553
|
+
} else {
|
|
1554
|
+
stats.losses++;
|
|
1555
|
+
stats.symbolStats[symbolName].losses++;
|
|
1556
|
+
ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
1557
|
+
}
|
|
1558
|
+
} else if (pnlTicks !== null) {
|
|
1559
|
+
// Log with ticks only
|
|
1560
|
+
stats.trades++;
|
|
1561
|
+
stats.symbolStats[symbolName].trades++;
|
|
1562
|
+
if (pnlTicks >= 0) {
|
|
1563
|
+
stats.wins++;
|
|
1564
|
+
stats.symbolStats[symbolName].wins++;
|
|
1565
|
+
ui.addLog('win', `[${symbolName}] +${pnlTicks} ticks | ${holdSec}s`);
|
|
1493
1566
|
} else {
|
|
1494
1567
|
stats.losses++;
|
|
1495
1568
|
stats.symbolStats[symbolName].losses++;
|
|
1496
|
-
ui.addLog('loss', `[${symbolName}]
|
|
1569
|
+
ui.addLog('loss', `[${symbolName}] ${pnlTicks} ticks | ${holdSec}s`);
|
|
1497
1570
|
}
|
|
1498
1571
|
}
|
|
1499
1572
|
stats.symbolStats[symbolName].position = 0;
|
|
1573
|
+
pendingOrders[symbolName] = false;
|
|
1500
1574
|
ui.render(stats);
|
|
1501
1575
|
});
|
|
1502
1576
|
|
|
1503
|
-
|
|
1577
|
+
pm.on('holdComplete', () => {
|
|
1578
|
+
ui.addLog('ready', `[${symbolName}] Hold complete - monitoring exit`);
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
pm.on('breakevenActivated', ({ breakevenPrice, pnlTicks }) => {
|
|
1582
|
+
ui.addLog('be', `[${symbolName}] BE @ ${breakevenPrice} | +${pnlTicks} ticks`);
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1586
|
+
// STRATEGY SIGNALS
|
|
1587
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1504
1588
|
strategy.on('signal', async (signal) => {
|
|
1505
1589
|
if (!running) return;
|
|
1590
|
+
if (pendingOrders[symbolName]) return;
|
|
1506
1591
|
if (!pm.canEnter(symbolName)) return;
|
|
1507
1592
|
|
|
1508
|
-
const { direction } = signal;
|
|
1593
|
+
const { direction, confidence } = signal;
|
|
1509
1594
|
const orderSide = direction === 'long' ? 0 : 1;
|
|
1510
1595
|
const sideStr = direction === 'long' ? 'LONG' : 'SHORT';
|
|
1511
1596
|
|
|
1512
|
-
|
|
1597
|
+
// Calculate risk amount
|
|
1598
|
+
const kelly = Math.min(0.25, confidence || 0.15);
|
|
1599
|
+
const riskAmount = Math.round(maxRisk * kelly);
|
|
1600
|
+
const riskPct = Math.round((riskAmount / maxRisk) * 100);
|
|
1601
|
+
|
|
1602
|
+
pendingOrders[symbolName] = true;
|
|
1603
|
+
ui.addLog('entry', `[${symbolName}] ${sideStr} ${qty}x | risk: $${riskAmount} (${riskPct}%)`);
|
|
1513
1604
|
|
|
1514
1605
|
const orderData = {
|
|
1515
1606
|
accountId: rithmicAccountId,
|
|
1516
1607
|
symbol: symbolName,
|
|
1517
|
-
exchange:
|
|
1608
|
+
exchange: contractInfoMap[symbolName].exchange,
|
|
1518
1609
|
size: qty,
|
|
1519
1610
|
side: orderSide,
|
|
1520
1611
|
};
|
|
1521
1612
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1613
|
+
try {
|
|
1614
|
+
const entryResult = service.fastEntry(orderData);
|
|
1615
|
+
if (entryResult.success) {
|
|
1616
|
+
pm.registerEntry(entryResult, orderData, contractInfoMap[symbolName]);
|
|
1617
|
+
|
|
1618
|
+
// Update avg entry latency
|
|
1619
|
+
stats.avgEntryLatency = stats.entryLatencies.length > 0
|
|
1620
|
+
? (stats.avgEntryLatency * stats.entryLatencies.length + entryResult.latencyMs) / (stats.entryLatencies.length + 1)
|
|
1621
|
+
: entryResult.latencyMs;
|
|
1622
|
+
} else {
|
|
1623
|
+
ui.addLog('error', `[${symbolName}] Entry failed: ${entryResult.error}`);
|
|
1624
|
+
pendingOrders[symbolName] = false;
|
|
1625
|
+
}
|
|
1626
|
+
} catch (e) {
|
|
1627
|
+
ui.addLog('error', `[${symbolName}] Order error: ${e.message}`);
|
|
1628
|
+
pendingOrders[symbolName] = false;
|
|
1527
1629
|
}
|
|
1528
1630
|
});
|
|
1529
1631
|
});
|
|
@@ -1535,13 +1637,14 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1535
1637
|
|
|
1536
1638
|
marketFeed.on('connected', () => {
|
|
1537
1639
|
stats.connected = true;
|
|
1538
|
-
|
|
1539
|
-
|
|
1640
|
+
algoLogger.dataConnected(ui, 'RTC');
|
|
1641
|
+
algoLogger.algoOperational(ui, 'RITHMIC');
|
|
1540
1642
|
});
|
|
1541
1643
|
|
|
1542
1644
|
marketFeed.on('tick', (tick) => {
|
|
1543
1645
|
if (!running) return;
|
|
1544
1646
|
|
|
1647
|
+
tickCount++;
|
|
1545
1648
|
stats.latency = tick.latency || 0;
|
|
1546
1649
|
|
|
1547
1650
|
// Route tick to correct strategy based on symbol
|
|
@@ -1551,14 +1654,29 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1551
1654
|
for (const [symbolName, strategy] of Object.entries(strategies)) {
|
|
1552
1655
|
const baseSymbol = symbolName.replace(/[A-Z]\d+$/, '');
|
|
1553
1656
|
if (tickSymbol === baseSymbol || tickSymbol === symbolName || symbolName.startsWith(tickSymbol)) {
|
|
1657
|
+
stats.symbolStats[symbolName].tickCount++;
|
|
1658
|
+
|
|
1659
|
+
// Log first tick per symbol
|
|
1660
|
+
if (stats.symbolStats[symbolName].tickCount === 1) {
|
|
1661
|
+
algoLogger.info(ui, 'FIRST TICK', `[${symbolName}] price=${tick.price} bid=${tick.bid} ask=${tick.ask}`);
|
|
1662
|
+
} else if (stats.symbolStats[symbolName].tickCount === 100) {
|
|
1663
|
+
algoLogger.info(ui, 'DATA FLOWING', `[${symbolName}] 100 ticks received`);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1554
1666
|
const tickData = {
|
|
1555
1667
|
contractId: tick.contractId || symbolName,
|
|
1556
1668
|
price: tick.price || tick.lastPrice || tick.bid,
|
|
1557
1669
|
bid: tick.bid,
|
|
1558
1670
|
ask: tick.ask,
|
|
1559
1671
|
volume: tick.volume || tick.size || 1,
|
|
1672
|
+
side: tick.lastTradeSide || tick.side || 'unknown',
|
|
1560
1673
|
timestamp: tick.timestamp || Date.now()
|
|
1561
1674
|
};
|
|
1675
|
+
|
|
1676
|
+
// Update last price
|
|
1677
|
+
stats.lastPrice = tickData.price;
|
|
1678
|
+
|
|
1679
|
+
// Feed tick to strategy
|
|
1562
1680
|
strategy.processTick(tickData);
|
|
1563
1681
|
|
|
1564
1682
|
// Update price for position manager
|
|
@@ -1567,6 +1685,18 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1567
1685
|
price: tickData.price,
|
|
1568
1686
|
timestamp: tickData.timestamp,
|
|
1569
1687
|
});
|
|
1688
|
+
|
|
1689
|
+
// Get momentum data from strategy for position manager
|
|
1690
|
+
const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(symbolName);
|
|
1691
|
+
if (modelValues && positionManagers[symbolName] && typeof positionManagers[symbolName].updateMomentum === 'function') {
|
|
1692
|
+
positionManagers[symbolName].updateMomentum(symbolName, {
|
|
1693
|
+
ofi: modelValues.ofi || 0,
|
|
1694
|
+
zscore: modelValues.zscore || 0,
|
|
1695
|
+
delta: modelValues.delta || 0,
|
|
1696
|
+
timestamp: tickData.timestamp,
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1570
1700
|
break;
|
|
1571
1701
|
}
|
|
1572
1702
|
}
|
|
@@ -1575,14 +1705,31 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1575
1705
|
});
|
|
1576
1706
|
|
|
1577
1707
|
marketFeed.on('error', (err) => {
|
|
1578
|
-
|
|
1708
|
+
algoLogger.error(ui, 'MARKET ERROR', err.message);
|
|
1579
1709
|
});
|
|
1580
1710
|
|
|
1581
|
-
|
|
1711
|
+
marketFeed.on('disconnected', (err) => {
|
|
1712
|
+
stats.connected = false;
|
|
1713
|
+
algoLogger.dataDisconnected(ui, 'WEBSOCKET', err?.message);
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1717
|
+
// STARTUP LOGS (same as launchAlgo)
|
|
1718
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1719
|
+
const market = checkMarketHours();
|
|
1720
|
+
const sessionName = market.session || 'AMERICAN';
|
|
1721
|
+
const etTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'America/New_York' });
|
|
1722
|
+
|
|
1723
|
+
algoLogger.connectingToEngine(ui, account.accountId);
|
|
1724
|
+
algoLogger.engineStarting(ui, 'RITHMIC', dailyTarget, maxRisk);
|
|
1725
|
+
algoLogger.marketOpen(ui, sessionName.toUpperCase(), etTime);
|
|
1726
|
+
algoLogger.info(ui, 'FAST PATH', `Rithmic direct | ${contracts.length} symbols | Target <${FAST_SCALPING.LATENCY_TARGET_MS}ms`);
|
|
1727
|
+
|
|
1582
1728
|
ui.render(stats);
|
|
1583
|
-
ui.addLog('info', `MULTI-SYMBOL: ${contracts.length} symbols`);
|
|
1584
1729
|
|
|
1730
|
+
// Connect and subscribe
|
|
1585
1731
|
try {
|
|
1732
|
+
algoLogger.info(ui, 'CONNECTING', `RITHMIC TICKER | ${contracts.length} symbols`);
|
|
1586
1733
|
await marketFeed.connect();
|
|
1587
1734
|
await new Promise(r => setTimeout(r, 1000));
|
|
1588
1735
|
|
|
@@ -1591,28 +1738,68 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1591
1738
|
const symbolName = contract.name || contract.symbol;
|
|
1592
1739
|
const exchange = contract.exchange || 'CME';
|
|
1593
1740
|
marketFeed.subscribe(symbolName, exchange);
|
|
1594
|
-
|
|
1741
|
+
algoLogger.info(ui, 'SUBSCRIBED', `${symbolName} (${exchange})`);
|
|
1595
1742
|
}
|
|
1596
1743
|
} else {
|
|
1597
|
-
|
|
1744
|
+
algoLogger.error(ui, 'CONNECTION', 'Failed to connect market feed');
|
|
1598
1745
|
}
|
|
1599
1746
|
} catch (e) {
|
|
1600
|
-
|
|
1747
|
+
algoLogger.error(ui, 'CONNECTION ERROR', e.message);
|
|
1601
1748
|
}
|
|
1602
1749
|
|
|
1603
1750
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1604
|
-
// P&L
|
|
1751
|
+
// REAL-TIME P&L VIA WEBSOCKET (same as launchAlgo)
|
|
1605
1752
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1606
1753
|
if (typeof service.on === 'function') {
|
|
1754
|
+
// Account-level P&L updates
|
|
1607
1755
|
service.on('pnlUpdate', (pnlData) => {
|
|
1608
1756
|
if (pnlData.accountId !== rithmicAccountId) return;
|
|
1757
|
+
|
|
1609
1758
|
if (pnlData.closedPositionPnl !== undefined) {
|
|
1610
1759
|
stats.closedPnl = parseFloat(pnlData.closedPositionPnl);
|
|
1611
1760
|
}
|
|
1612
1761
|
if (pnlData.accountBalance !== undefined) {
|
|
1613
1762
|
stats.balance = parseFloat(pnlData.accountBalance);
|
|
1614
1763
|
}
|
|
1764
|
+
if (pnlData.availableBuyingPower !== undefined) {
|
|
1765
|
+
stats.buyingPower = parseFloat(pnlData.availableBuyingPower);
|
|
1766
|
+
}
|
|
1767
|
+
if (pnlData.marginBalance !== undefined) {
|
|
1768
|
+
stats.margin = parseFloat(pnlData.marginBalance);
|
|
1769
|
+
}
|
|
1770
|
+
if (pnlData.netLiquidation !== undefined) {
|
|
1771
|
+
stats.netLiquidation = parseFloat(pnlData.netLiquidation);
|
|
1772
|
+
} else if (stats.balance !== null) {
|
|
1773
|
+
stats.netLiquidation = stats.balance + (stats.openPnl || 0);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1615
1776
|
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
1777
|
+
ui.render(stats);
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
// Position-level updates (for Open P&L)
|
|
1781
|
+
service.on('positionUpdate', (pos) => {
|
|
1782
|
+
if (!pos || pos.accountId !== rithmicAccountId) return;
|
|
1783
|
+
|
|
1784
|
+
const posSymbol = pos.symbol;
|
|
1785
|
+
for (const symbolName of Object.keys(stats.symbolStats)) {
|
|
1786
|
+
const baseSymbol = symbolName.replace(/[A-Z]\d+$/, '');
|
|
1787
|
+
if (posSymbol === baseSymbol || posSymbol === symbolName || symbolName.startsWith(posSymbol)) {
|
|
1788
|
+
stats.symbolStats[symbolName].position = pos.quantity || 0;
|
|
1789
|
+
|
|
1790
|
+
// Update Open P&L from position
|
|
1791
|
+
if (pos.openPnl !== undefined && pos.openPnl !== null) {
|
|
1792
|
+
stats.openPnl = pos.openPnl;
|
|
1793
|
+
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
1794
|
+
|
|
1795
|
+
if (stats.balance !== null) {
|
|
1796
|
+
stats.netLiquidation = stats.balance + stats.openPnl;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
ui.render(stats);
|
|
1616
1803
|
});
|
|
1617
1804
|
}
|
|
1618
1805
|
|