hedgequantx 2.6.128 → 2.6.130
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 +253 -40
package/package.json
CHANGED
|
@@ -181,17 +181,18 @@ const selectSymbol = async (service, account, allowMultiple = false) => {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// Multi-symbol selection mode
|
|
184
|
+
// Each contract will have a 'qty' property for number of contracts
|
|
184
185
|
const selectedContracts = [];
|
|
185
186
|
|
|
186
187
|
while (selectedContracts.length < MAX_MULTI_SYMBOLS) {
|
|
187
188
|
console.log();
|
|
188
189
|
|
|
189
|
-
// Show already selected symbols
|
|
190
|
+
// Show already selected symbols with quantities
|
|
190
191
|
if (selectedContracts.length > 0) {
|
|
191
192
|
console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_MULTI_SYMBOLS}):`));
|
|
192
193
|
selectedContracts.forEach((c, i) => {
|
|
193
194
|
const name = c.name || c.symbol;
|
|
194
|
-
console.log(chalk.green(` ${i + 1}. ${name}`));
|
|
195
|
+
console.log(chalk.green(` ${i + 1}. ${name} x${c.qty}`));
|
|
195
196
|
});
|
|
196
197
|
console.log();
|
|
197
198
|
}
|
|
@@ -225,6 +226,13 @@ const selectSymbol = async (service, account, allowMultiple = false) => {
|
|
|
225
226
|
|
|
226
227
|
if (choice === 'done') break;
|
|
227
228
|
|
|
229
|
+
// Ask for number of contracts for this symbol
|
|
230
|
+
const symbolName = choice.name || choice.symbol;
|
|
231
|
+
const qty = await prompts.numberInput(`CONTRACTS FOR ${symbolName}:`, 1, 1, 10);
|
|
232
|
+
if (qty === null) continue; // User cancelled, don't add symbol
|
|
233
|
+
|
|
234
|
+
// Add qty to contract object
|
|
235
|
+
choice.qty = qty;
|
|
228
236
|
selectedContracts.push(choice);
|
|
229
237
|
|
|
230
238
|
if (selectedContracts.length >= MAX_MULTI_SYMBOLS) {
|
|
@@ -243,21 +251,29 @@ const selectSymbol = async (service, account, allowMultiple = false) => {
|
|
|
243
251
|
*/
|
|
244
252
|
const configureAlgo = async (account, contractOrContracts) => {
|
|
245
253
|
const contractList = Array.isArray(contractOrContracts) ? contractOrContracts : [contractOrContracts];
|
|
254
|
+
const isMultiSymbol = contractList.length > 1;
|
|
246
255
|
|
|
247
256
|
console.log();
|
|
248
257
|
console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
|
|
249
258
|
|
|
250
|
-
// Show selected symbols
|
|
251
|
-
if (
|
|
259
|
+
// Show selected symbols with quantities (multi-symbol mode)
|
|
260
|
+
if (isMultiSymbol) {
|
|
252
261
|
console.log(chalk.white(` Trading ${contractList.length} symbols:`));
|
|
253
262
|
contractList.forEach((c, i) => {
|
|
254
|
-
|
|
263
|
+
const name = c.name || c.symbol;
|
|
264
|
+
const qty = c.qty || 1;
|
|
265
|
+
console.log(chalk.yellow(` ${i + 1}. ${name} x${qty}`));
|
|
255
266
|
});
|
|
256
267
|
}
|
|
257
268
|
console.log();
|
|
258
269
|
|
|
259
|
-
|
|
260
|
-
|
|
270
|
+
// Only ask for contracts in single-symbol mode
|
|
271
|
+
// In multi-symbol mode, qty is already set per symbol
|
|
272
|
+
let contracts = 1;
|
|
273
|
+
if (!isMultiSymbol) {
|
|
274
|
+
contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
|
|
275
|
+
if (contracts === null) return null;
|
|
276
|
+
}
|
|
261
277
|
|
|
262
278
|
const dailyTarget = await prompts.numberInput('DAILY TARGET ($):', 1000, 1, 10000);
|
|
263
279
|
if (dailyTarget === null) return null;
|
|
@@ -1385,33 +1401,41 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
1385
1401
|
* Uses single market feed connection with multiple subscriptions
|
|
1386
1402
|
* Each symbol has its own PositionManager and strategy instance
|
|
1387
1403
|
*
|
|
1404
|
+
* Same logic as launchAlgo but for multiple symbols
|
|
1405
|
+
*
|
|
1388
1406
|
* @param {Object} service - Rithmic trading service
|
|
1389
1407
|
* @param {Object} account - Trading account
|
|
1390
1408
|
* @param {Array} contracts - Array of contracts to trade
|
|
1391
1409
|
* @param {Object} config - Algo configuration
|
|
1392
1410
|
*/
|
|
1393
1411
|
const launchMultiSymbolRithmic = async (service, account, contracts, config) => {
|
|
1394
|
-
const {
|
|
1412
|
+
const { dailyTarget, maxRisk, showName, enableAI } = config;
|
|
1395
1413
|
|
|
1396
1414
|
const accountName = showName
|
|
1397
1415
|
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
1398
1416
|
: 'HQX *****';
|
|
1399
1417
|
const rithmicAccountId = account.rithmicAccountId || account.accountId;
|
|
1400
1418
|
|
|
1401
|
-
// Build symbols string for UI
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1419
|
+
// Build symbols string for UI (with quantities)
|
|
1420
|
+
const symbolsDisplay = contracts.map(c => {
|
|
1421
|
+
const name = c.name || c.symbol;
|
|
1422
|
+
const qty = c.qty || 1;
|
|
1423
|
+
return `${name}x${qty}`;
|
|
1424
|
+
}).join(', ');
|
|
1404
1425
|
|
|
1405
1426
|
const ui = new AlgoUI({
|
|
1406
1427
|
subtitle: `MULTI-SYMBOL (${contracts.length})`,
|
|
1407
1428
|
mode: 'one-account'
|
|
1408
1429
|
});
|
|
1409
1430
|
|
|
1410
|
-
//
|
|
1431
|
+
// Calculate total qty across all symbols
|
|
1432
|
+
const totalQty = contracts.reduce((sum, c) => sum + (c.qty || 1), 0);
|
|
1433
|
+
|
|
1434
|
+
// Shared stats (same structure as launchAlgo)
|
|
1411
1435
|
const stats = {
|
|
1412
1436
|
accountName,
|
|
1413
1437
|
symbol: symbolsDisplay,
|
|
1414
|
-
qty,
|
|
1438
|
+
qty: totalQty,
|
|
1415
1439
|
target: dailyTarget,
|
|
1416
1440
|
risk: maxRisk,
|
|
1417
1441
|
propfirm: account.propfirm || 'Unknown',
|
|
@@ -1420,7 +1444,12 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1420
1444
|
openPnl: null,
|
|
1421
1445
|
closedPnl: null,
|
|
1422
1446
|
balance: null,
|
|
1447
|
+
buyingPower: null,
|
|
1448
|
+
margin: null,
|
|
1449
|
+
netLiquidation: null,
|
|
1423
1450
|
position: 0,
|
|
1451
|
+
entryPrice: 0,
|
|
1452
|
+
lastPrice: 0,
|
|
1424
1453
|
trades: 0,
|
|
1425
1454
|
wins: 0,
|
|
1426
1455
|
losses: 0,
|
|
@@ -1429,7 +1458,12 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1429
1458
|
connected: false,
|
|
1430
1459
|
startTime: Date.now(),
|
|
1431
1460
|
aiSupervision: enableAI || false,
|
|
1461
|
+
aiMode: null,
|
|
1462
|
+
agentCount: 0,
|
|
1432
1463
|
fastPath: true,
|
|
1464
|
+
avgEntryLatency: 0,
|
|
1465
|
+
avgFillLatency: 0,
|
|
1466
|
+
entryLatencies: [],
|
|
1433
1467
|
// Per-symbol tracking
|
|
1434
1468
|
symbolStats: {},
|
|
1435
1469
|
};
|
|
@@ -1437,23 +1471,43 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1437
1471
|
// Initialize per-symbol stats
|
|
1438
1472
|
contracts.forEach(c => {
|
|
1439
1473
|
const name = c.name || c.symbol;
|
|
1440
|
-
stats.symbolStats[name] = {
|
|
1474
|
+
stats.symbolStats[name] = {
|
|
1475
|
+
position: 0,
|
|
1476
|
+
trades: 0,
|
|
1477
|
+
wins: 0,
|
|
1478
|
+
losses: 0,
|
|
1479
|
+
pnl: 0,
|
|
1480
|
+
tickCount: 0,
|
|
1481
|
+
};
|
|
1441
1482
|
});
|
|
1442
1483
|
|
|
1443
1484
|
let running = true;
|
|
1444
1485
|
let stopReason = null;
|
|
1486
|
+
let tickCount = 0;
|
|
1487
|
+
|
|
1488
|
+
// Store contract info for later use (including qty per symbol)
|
|
1489
|
+
const contractInfoMap = {};
|
|
1490
|
+
contracts.forEach(c => {
|
|
1491
|
+
const name = c.name || c.symbol;
|
|
1492
|
+
contractInfoMap[name] = {
|
|
1493
|
+
tickSize: c.tickSize ?? null,
|
|
1494
|
+
tickValue: c.tickValue ?? null,
|
|
1495
|
+
contractId: c.id || c.symbol || c.name,
|
|
1496
|
+
exchange: c.exchange || 'CME',
|
|
1497
|
+
qty: c.qty || 1, // Per-symbol quantity
|
|
1498
|
+
};
|
|
1499
|
+
});
|
|
1445
1500
|
|
|
1446
1501
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1447
|
-
// POSITION MANAGERS - One per symbol
|
|
1502
|
+
// POSITION MANAGERS & STRATEGIES - One per symbol
|
|
1448
1503
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1449
1504
|
const positionManagers = {};
|
|
1450
1505
|
const strategies = {};
|
|
1506
|
+
const pendingOrders = {}; // Track pending orders per symbol
|
|
1451
1507
|
|
|
1452
1508
|
contracts.forEach(contract => {
|
|
1453
1509
|
const symbolName = contract.name || contract.symbol;
|
|
1454
|
-
const
|
|
1455
|
-
const tickSize = contract.tickSize ?? null;
|
|
1456
|
-
const tickValue = contract.tickValue ?? null;
|
|
1510
|
+
const { tickSize, tickValue, contractId } = contractInfoMap[symbolName];
|
|
1457
1511
|
|
|
1458
1512
|
// Create strategy instance for this symbol
|
|
1459
1513
|
const strategy = Object.create(hftStrategy);
|
|
@@ -1461,6 +1515,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1461
1515
|
strategy.initialize(contractId, tickSize, tickValue);
|
|
1462
1516
|
}
|
|
1463
1517
|
strategies[symbolName] = strategy;
|
|
1518
|
+
pendingOrders[symbolName] = false;
|
|
1464
1519
|
|
|
1465
1520
|
// Create position manager for this symbol
|
|
1466
1521
|
const pm = new PositionManager(service, strategy);
|
|
@@ -1470,11 +1525,16 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1470
1525
|
pm.start();
|
|
1471
1526
|
positionManagers[symbolName] = pm;
|
|
1472
1527
|
|
|
1473
|
-
//
|
|
1528
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1529
|
+
// POSITION MANAGER EVENTS (same as launchAlgo)
|
|
1530
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1474
1531
|
pm.on('entryFilled', ({ position, fillLatencyMs }) => {
|
|
1532
|
+
stats.entryLatencies.push(fillLatencyMs);
|
|
1533
|
+
stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
|
|
1475
1534
|
const side = position.side === 0 ? 'LONG' : 'SHORT';
|
|
1476
1535
|
ui.addLog('filled', `[${symbolName}] ${side} ${position.size}x @ ${position.entryPrice} | ${fillLatencyMs}ms`);
|
|
1477
1536
|
stats.symbolStats[symbolName].position = position.side === 0 ? position.size : -position.size;
|
|
1537
|
+
ui.render(stats);
|
|
1478
1538
|
});
|
|
1479
1539
|
|
|
1480
1540
|
pm.on('exitFilled', ({ exitPrice, pnlTicks, holdDurationMs }) => {
|
|
@@ -1486,44 +1546,112 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1486
1546
|
stats.symbolStats[symbolName].trades++;
|
|
1487
1547
|
stats.trades++;
|
|
1488
1548
|
|
|
1549
|
+
// Record trade for Recovery Math
|
|
1550
|
+
recoveryMath.recordTrade({
|
|
1551
|
+
pnl: pnlDollars,
|
|
1552
|
+
ticks: pnlTicks,
|
|
1553
|
+
side: pnlTicks >= 0 ? 'win' : 'loss',
|
|
1554
|
+
duration: holdDurationMs,
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
// Update Recovery Mode state
|
|
1558
|
+
const recovery = recoveryMath.updateSessionPnL(
|
|
1559
|
+
stats.sessionPnl,
|
|
1560
|
+
FAST_SCALPING.RECOVERY?.ACTIVATION_PNL || -300,
|
|
1561
|
+
FAST_SCALPING.RECOVERY?.DEACTIVATION_PNL || -100
|
|
1562
|
+
);
|
|
1563
|
+
|
|
1564
|
+
if (recovery.justActivated) {
|
|
1565
|
+
stats.recoveryMode = true;
|
|
1566
|
+
ui.addLog('warning', `RECOVERY MODE ON - Kelly: ${(recoveryMath.calcKelly() * 100).toFixed(0)}%`);
|
|
1567
|
+
} else if (recovery.justDeactivated) {
|
|
1568
|
+
stats.recoveryMode = false;
|
|
1569
|
+
ui.addLog('success', `RECOVERY MODE OFF - Session P&L: $${stats.sessionPnl.toFixed(2)}`);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1489
1572
|
if (pnlDollars >= 0) {
|
|
1490
1573
|
stats.wins++;
|
|
1491
1574
|
stats.symbolStats[symbolName].wins++;
|
|
1492
|
-
ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} | ${holdSec}s`);
|
|
1575
|
+
ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
1493
1576
|
} else {
|
|
1494
1577
|
stats.losses++;
|
|
1495
1578
|
stats.symbolStats[symbolName].losses++;
|
|
1496
|
-
ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} | ${holdSec}s`);
|
|
1579
|
+
ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
1580
|
+
}
|
|
1581
|
+
} else if (pnlTicks !== null) {
|
|
1582
|
+
// Log with ticks only
|
|
1583
|
+
stats.trades++;
|
|
1584
|
+
stats.symbolStats[symbolName].trades++;
|
|
1585
|
+
if (pnlTicks >= 0) {
|
|
1586
|
+
stats.wins++;
|
|
1587
|
+
stats.symbolStats[symbolName].wins++;
|
|
1588
|
+
ui.addLog('win', `[${symbolName}] +${pnlTicks} ticks | ${holdSec}s`);
|
|
1589
|
+
} else {
|
|
1590
|
+
stats.losses++;
|
|
1591
|
+
stats.symbolStats[symbolName].losses++;
|
|
1592
|
+
ui.addLog('loss', `[${symbolName}] ${pnlTicks} ticks | ${holdSec}s`);
|
|
1497
1593
|
}
|
|
1498
1594
|
}
|
|
1499
1595
|
stats.symbolStats[symbolName].position = 0;
|
|
1596
|
+
pendingOrders[symbolName] = false;
|
|
1500
1597
|
ui.render(stats);
|
|
1501
1598
|
});
|
|
1502
1599
|
|
|
1503
|
-
|
|
1600
|
+
pm.on('holdComplete', () => {
|
|
1601
|
+
ui.addLog('ready', `[${symbolName}] Hold complete - monitoring exit`);
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
pm.on('breakevenActivated', ({ breakevenPrice, pnlTicks }) => {
|
|
1605
|
+
ui.addLog('be', `[${symbolName}] BE @ ${breakevenPrice} | +${pnlTicks} ticks`);
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1609
|
+
// STRATEGY SIGNALS
|
|
1610
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1504
1611
|
strategy.on('signal', async (signal) => {
|
|
1505
1612
|
if (!running) return;
|
|
1613
|
+
if (pendingOrders[symbolName]) return;
|
|
1506
1614
|
if (!pm.canEnter(symbolName)) return;
|
|
1507
1615
|
|
|
1508
|
-
const { direction } = signal;
|
|
1616
|
+
const { direction, confidence } = signal;
|
|
1509
1617
|
const orderSide = direction === 'long' ? 0 : 1;
|
|
1510
1618
|
const sideStr = direction === 'long' ? 'LONG' : 'SHORT';
|
|
1511
1619
|
|
|
1512
|
-
|
|
1620
|
+
// Use per-symbol quantity
|
|
1621
|
+
const symbolQty = contractInfoMap[symbolName].qty;
|
|
1622
|
+
|
|
1623
|
+
// Calculate risk amount
|
|
1624
|
+
const kelly = Math.min(0.25, confidence || 0.15);
|
|
1625
|
+
const riskAmount = Math.round(maxRisk * kelly);
|
|
1626
|
+
const riskPct = Math.round((riskAmount / maxRisk) * 100);
|
|
1627
|
+
|
|
1628
|
+
pendingOrders[symbolName] = true;
|
|
1629
|
+
ui.addLog('entry', `[${symbolName}] ${sideStr} ${symbolQty}x | risk: $${riskAmount} (${riskPct}%)`);
|
|
1513
1630
|
|
|
1514
1631
|
const orderData = {
|
|
1515
1632
|
accountId: rithmicAccountId,
|
|
1516
1633
|
symbol: symbolName,
|
|
1517
|
-
exchange:
|
|
1518
|
-
size:
|
|
1634
|
+
exchange: contractInfoMap[symbolName].exchange,
|
|
1635
|
+
size: symbolQty,
|
|
1519
1636
|
side: orderSide,
|
|
1520
1637
|
};
|
|
1521
1638
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1639
|
+
try {
|
|
1640
|
+
const entryResult = service.fastEntry(orderData);
|
|
1641
|
+
if (entryResult.success) {
|
|
1642
|
+
pm.registerEntry(entryResult, orderData, contractInfoMap[symbolName]);
|
|
1643
|
+
|
|
1644
|
+
// Update avg entry latency
|
|
1645
|
+
stats.avgEntryLatency = stats.entryLatencies.length > 0
|
|
1646
|
+
? (stats.avgEntryLatency * stats.entryLatencies.length + entryResult.latencyMs) / (stats.entryLatencies.length + 1)
|
|
1647
|
+
: entryResult.latencyMs;
|
|
1648
|
+
} else {
|
|
1649
|
+
ui.addLog('error', `[${symbolName}] Entry failed: ${entryResult.error}`);
|
|
1650
|
+
pendingOrders[symbolName] = false;
|
|
1651
|
+
}
|
|
1652
|
+
} catch (e) {
|
|
1653
|
+
ui.addLog('error', `[${symbolName}] Order error: ${e.message}`);
|
|
1654
|
+
pendingOrders[symbolName] = false;
|
|
1527
1655
|
}
|
|
1528
1656
|
});
|
|
1529
1657
|
});
|
|
@@ -1535,13 +1663,14 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1535
1663
|
|
|
1536
1664
|
marketFeed.on('connected', () => {
|
|
1537
1665
|
stats.connected = true;
|
|
1538
|
-
|
|
1539
|
-
|
|
1666
|
+
algoLogger.dataConnected(ui, 'RTC');
|
|
1667
|
+
algoLogger.algoOperational(ui, 'RITHMIC');
|
|
1540
1668
|
});
|
|
1541
1669
|
|
|
1542
1670
|
marketFeed.on('tick', (tick) => {
|
|
1543
1671
|
if (!running) return;
|
|
1544
1672
|
|
|
1673
|
+
tickCount++;
|
|
1545
1674
|
stats.latency = tick.latency || 0;
|
|
1546
1675
|
|
|
1547
1676
|
// Route tick to correct strategy based on symbol
|
|
@@ -1551,14 +1680,29 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1551
1680
|
for (const [symbolName, strategy] of Object.entries(strategies)) {
|
|
1552
1681
|
const baseSymbol = symbolName.replace(/[A-Z]\d+$/, '');
|
|
1553
1682
|
if (tickSymbol === baseSymbol || tickSymbol === symbolName || symbolName.startsWith(tickSymbol)) {
|
|
1683
|
+
stats.symbolStats[symbolName].tickCount++;
|
|
1684
|
+
|
|
1685
|
+
// Log first tick per symbol
|
|
1686
|
+
if (stats.symbolStats[symbolName].tickCount === 1) {
|
|
1687
|
+
algoLogger.info(ui, 'FIRST TICK', `[${symbolName}] price=${tick.price} bid=${tick.bid} ask=${tick.ask}`);
|
|
1688
|
+
} else if (stats.symbolStats[symbolName].tickCount === 100) {
|
|
1689
|
+
algoLogger.info(ui, 'DATA FLOWING', `[${symbolName}] 100 ticks received`);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1554
1692
|
const tickData = {
|
|
1555
1693
|
contractId: tick.contractId || symbolName,
|
|
1556
1694
|
price: tick.price || tick.lastPrice || tick.bid,
|
|
1557
1695
|
bid: tick.bid,
|
|
1558
1696
|
ask: tick.ask,
|
|
1559
1697
|
volume: tick.volume || tick.size || 1,
|
|
1698
|
+
side: tick.lastTradeSide || tick.side || 'unknown',
|
|
1560
1699
|
timestamp: tick.timestamp || Date.now()
|
|
1561
1700
|
};
|
|
1701
|
+
|
|
1702
|
+
// Update last price
|
|
1703
|
+
stats.lastPrice = tickData.price;
|
|
1704
|
+
|
|
1705
|
+
// Feed tick to strategy
|
|
1562
1706
|
strategy.processTick(tickData);
|
|
1563
1707
|
|
|
1564
1708
|
// Update price for position manager
|
|
@@ -1567,6 +1711,18 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1567
1711
|
price: tickData.price,
|
|
1568
1712
|
timestamp: tickData.timestamp,
|
|
1569
1713
|
});
|
|
1714
|
+
|
|
1715
|
+
// Get momentum data from strategy for position manager
|
|
1716
|
+
const modelValues = strategy.getModelValues?.() || strategy.getModelValues?.(symbolName);
|
|
1717
|
+
if (modelValues && positionManagers[symbolName] && typeof positionManagers[symbolName].updateMomentum === 'function') {
|
|
1718
|
+
positionManagers[symbolName].updateMomentum(symbolName, {
|
|
1719
|
+
ofi: modelValues.ofi || 0,
|
|
1720
|
+
zscore: modelValues.zscore || 0,
|
|
1721
|
+
delta: modelValues.delta || 0,
|
|
1722
|
+
timestamp: tickData.timestamp,
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1570
1726
|
break;
|
|
1571
1727
|
}
|
|
1572
1728
|
}
|
|
@@ -1575,14 +1731,31 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1575
1731
|
});
|
|
1576
1732
|
|
|
1577
1733
|
marketFeed.on('error', (err) => {
|
|
1578
|
-
|
|
1734
|
+
algoLogger.error(ui, 'MARKET ERROR', err.message);
|
|
1579
1735
|
});
|
|
1580
1736
|
|
|
1581
|
-
|
|
1737
|
+
marketFeed.on('disconnected', (err) => {
|
|
1738
|
+
stats.connected = false;
|
|
1739
|
+
algoLogger.dataDisconnected(ui, 'WEBSOCKET', err?.message);
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1743
|
+
// STARTUP LOGS (same as launchAlgo)
|
|
1744
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1745
|
+
const market = checkMarketHours();
|
|
1746
|
+
const sessionName = market.session || 'AMERICAN';
|
|
1747
|
+
const etTime = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'America/New_York' });
|
|
1748
|
+
|
|
1749
|
+
algoLogger.connectingToEngine(ui, account.accountId);
|
|
1750
|
+
algoLogger.engineStarting(ui, 'RITHMIC', dailyTarget, maxRisk);
|
|
1751
|
+
algoLogger.marketOpen(ui, sessionName.toUpperCase(), etTime);
|
|
1752
|
+
algoLogger.info(ui, 'FAST PATH', `Rithmic direct | ${contracts.length} symbols | Target <${FAST_SCALPING.LATENCY_TARGET_MS}ms`);
|
|
1753
|
+
|
|
1582
1754
|
ui.render(stats);
|
|
1583
|
-
ui.addLog('info', `MULTI-SYMBOL: ${contracts.length} symbols`);
|
|
1584
1755
|
|
|
1756
|
+
// Connect and subscribe
|
|
1585
1757
|
try {
|
|
1758
|
+
algoLogger.info(ui, 'CONNECTING', `RITHMIC TICKER | ${contracts.length} symbols`);
|
|
1586
1759
|
await marketFeed.connect();
|
|
1587
1760
|
await new Promise(r => setTimeout(r, 1000));
|
|
1588
1761
|
|
|
@@ -1591,28 +1764,68 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1591
1764
|
const symbolName = contract.name || contract.symbol;
|
|
1592
1765
|
const exchange = contract.exchange || 'CME';
|
|
1593
1766
|
marketFeed.subscribe(symbolName, exchange);
|
|
1594
|
-
|
|
1767
|
+
algoLogger.info(ui, 'SUBSCRIBED', `${symbolName} (${exchange})`);
|
|
1595
1768
|
}
|
|
1596
1769
|
} else {
|
|
1597
|
-
|
|
1770
|
+
algoLogger.error(ui, 'CONNECTION', 'Failed to connect market feed');
|
|
1598
1771
|
}
|
|
1599
1772
|
} catch (e) {
|
|
1600
|
-
|
|
1773
|
+
algoLogger.error(ui, 'CONNECTION ERROR', e.message);
|
|
1601
1774
|
}
|
|
1602
1775
|
|
|
1603
1776
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1604
|
-
// P&L
|
|
1777
|
+
// REAL-TIME P&L VIA WEBSOCKET (same as launchAlgo)
|
|
1605
1778
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1606
1779
|
if (typeof service.on === 'function') {
|
|
1780
|
+
// Account-level P&L updates
|
|
1607
1781
|
service.on('pnlUpdate', (pnlData) => {
|
|
1608
1782
|
if (pnlData.accountId !== rithmicAccountId) return;
|
|
1783
|
+
|
|
1609
1784
|
if (pnlData.closedPositionPnl !== undefined) {
|
|
1610
1785
|
stats.closedPnl = parseFloat(pnlData.closedPositionPnl);
|
|
1611
1786
|
}
|
|
1612
1787
|
if (pnlData.accountBalance !== undefined) {
|
|
1613
1788
|
stats.balance = parseFloat(pnlData.accountBalance);
|
|
1614
1789
|
}
|
|
1790
|
+
if (pnlData.availableBuyingPower !== undefined) {
|
|
1791
|
+
stats.buyingPower = parseFloat(pnlData.availableBuyingPower);
|
|
1792
|
+
}
|
|
1793
|
+
if (pnlData.marginBalance !== undefined) {
|
|
1794
|
+
stats.margin = parseFloat(pnlData.marginBalance);
|
|
1795
|
+
}
|
|
1796
|
+
if (pnlData.netLiquidation !== undefined) {
|
|
1797
|
+
stats.netLiquidation = parseFloat(pnlData.netLiquidation);
|
|
1798
|
+
} else if (stats.balance !== null) {
|
|
1799
|
+
stats.netLiquidation = stats.balance + (stats.openPnl || 0);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1615
1802
|
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
1803
|
+
ui.render(stats);
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
// Position-level updates (for Open P&L)
|
|
1807
|
+
service.on('positionUpdate', (pos) => {
|
|
1808
|
+
if (!pos || pos.accountId !== rithmicAccountId) return;
|
|
1809
|
+
|
|
1810
|
+
const posSymbol = pos.symbol;
|
|
1811
|
+
for (const symbolName of Object.keys(stats.symbolStats)) {
|
|
1812
|
+
const baseSymbol = symbolName.replace(/[A-Z]\d+$/, '');
|
|
1813
|
+
if (posSymbol === baseSymbol || posSymbol === symbolName || symbolName.startsWith(posSymbol)) {
|
|
1814
|
+
stats.symbolStats[symbolName].position = pos.quantity || 0;
|
|
1815
|
+
|
|
1816
|
+
// Update Open P&L from position
|
|
1817
|
+
if (pos.openPnl !== undefined && pos.openPnl !== null) {
|
|
1818
|
+
stats.openPnl = pos.openPnl;
|
|
1819
|
+
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
1820
|
+
|
|
1821
|
+
if (stats.balance !== null) {
|
|
1822
|
+
stats.netLiquidation = stats.balance + stats.openPnl;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
ui.render(stats);
|
|
1616
1829
|
});
|
|
1617
1830
|
}
|
|
1618
1831
|
|