hedgequantx 2.6.129 → 2.6.131
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 +96 -18
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;
|
|
@@ -1393,27 +1409,33 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
1393
1409
|
* @param {Object} config - Algo configuration
|
|
1394
1410
|
*/
|
|
1395
1411
|
const launchMultiSymbolRithmic = async (service, account, contracts, config) => {
|
|
1396
|
-
const {
|
|
1412
|
+
const { dailyTarget, maxRisk, showName, enableAI } = config;
|
|
1397
1413
|
|
|
1398
1414
|
const accountName = showName
|
|
1399
1415
|
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
1400
1416
|
: 'HQX *****';
|
|
1401
1417
|
const rithmicAccountId = account.rithmicAccountId || account.accountId;
|
|
1402
1418
|
|
|
1403
|
-
// Build symbols string for UI
|
|
1404
|
-
const
|
|
1405
|
-
|
|
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(', ');
|
|
1406
1425
|
|
|
1407
1426
|
const ui = new AlgoUI({
|
|
1408
1427
|
subtitle: `MULTI-SYMBOL (${contracts.length})`,
|
|
1409
1428
|
mode: 'one-account'
|
|
1410
1429
|
});
|
|
1411
1430
|
|
|
1431
|
+
// Calculate total qty across all symbols
|
|
1432
|
+
const totalQty = contracts.reduce((sum, c) => sum + (c.qty || 1), 0);
|
|
1433
|
+
|
|
1412
1434
|
// Shared stats (same structure as launchAlgo)
|
|
1413
1435
|
const stats = {
|
|
1414
1436
|
accountName,
|
|
1415
1437
|
symbol: symbolsDisplay,
|
|
1416
|
-
qty,
|
|
1438
|
+
qty: totalQty,
|
|
1417
1439
|
target: dailyTarget,
|
|
1418
1440
|
risk: maxRisk,
|
|
1419
1441
|
propfirm: account.propfirm || 'Unknown',
|
|
@@ -1455,6 +1477,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1455
1477
|
wins: 0,
|
|
1456
1478
|
losses: 0,
|
|
1457
1479
|
pnl: 0,
|
|
1480
|
+
openPnl: 0, // Per-symbol unrealized P&L
|
|
1458
1481
|
tickCount: 0,
|
|
1459
1482
|
};
|
|
1460
1483
|
});
|
|
@@ -1463,7 +1486,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1463
1486
|
let stopReason = null;
|
|
1464
1487
|
let tickCount = 0;
|
|
1465
1488
|
|
|
1466
|
-
// Store contract info for later use
|
|
1489
|
+
// Store contract info for later use (including qty per symbol)
|
|
1467
1490
|
const contractInfoMap = {};
|
|
1468
1491
|
contracts.forEach(c => {
|
|
1469
1492
|
const name = c.name || c.symbol;
|
|
@@ -1472,6 +1495,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1472
1495
|
tickValue: c.tickValue ?? null,
|
|
1473
1496
|
contractId: c.id || c.symbol || c.name,
|
|
1474
1497
|
exchange: c.exchange || 'CME',
|
|
1498
|
+
qty: c.qty || 1, // Per-symbol quantity
|
|
1475
1499
|
};
|
|
1476
1500
|
});
|
|
1477
1501
|
|
|
@@ -1481,6 +1505,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1481
1505
|
const positionManagers = {};
|
|
1482
1506
|
const strategies = {};
|
|
1483
1507
|
const pendingOrders = {}; // Track pending orders per symbol
|
|
1508
|
+
const disabledSymbols = new Set(); // Symbols disabled after SL hit
|
|
1484
1509
|
|
|
1485
1510
|
contracts.forEach(contract => {
|
|
1486
1511
|
const symbolName = contract.name || contract.symbol;
|
|
@@ -1554,6 +1579,11 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1554
1579
|
stats.losses++;
|
|
1555
1580
|
stats.symbolStats[symbolName].losses++;
|
|
1556
1581
|
ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
1582
|
+
|
|
1583
|
+
// Disable symbol after loss (SL hit) - other symbols continue
|
|
1584
|
+
disabledSymbols.add(symbolName);
|
|
1585
|
+
ui.addLog('warning', `[${symbolName}] DISABLED - SL hit, other symbols continue`);
|
|
1586
|
+
stats.activeSymbols = contracts.length - disabledSymbols.size;
|
|
1557
1587
|
}
|
|
1558
1588
|
} else if (pnlTicks !== null) {
|
|
1559
1589
|
// Log with ticks only
|
|
@@ -1567,9 +1597,17 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1567
1597
|
stats.losses++;
|
|
1568
1598
|
stats.symbolStats[symbolName].losses++;
|
|
1569
1599
|
ui.addLog('loss', `[${symbolName}] ${pnlTicks} ticks | ${holdSec}s`);
|
|
1600
|
+
|
|
1601
|
+
// Disable symbol after loss (SL hit) - other symbols continue
|
|
1602
|
+
disabledSymbols.add(symbolName);
|
|
1603
|
+
ui.addLog('warning', `[${symbolName}] DISABLED - SL hit, other symbols continue`);
|
|
1604
|
+
stats.activeSymbols = contracts.length - disabledSymbols.size;
|
|
1570
1605
|
}
|
|
1571
1606
|
}
|
|
1572
1607
|
stats.symbolStats[symbolName].position = 0;
|
|
1608
|
+
stats.symbolStats[symbolName].openPnl = 0; // Reset open P&L when position closed
|
|
1609
|
+
// Recalculate total Open P&L
|
|
1610
|
+
stats.openPnl = Object.values(stats.symbolStats).reduce((sum, s) => sum + (s.openPnl || 0), 0);
|
|
1573
1611
|
pendingOrders[symbolName] = false;
|
|
1574
1612
|
ui.render(stats);
|
|
1575
1613
|
});
|
|
@@ -1587,6 +1625,7 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1587
1625
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1588
1626
|
strategy.on('signal', async (signal) => {
|
|
1589
1627
|
if (!running) return;
|
|
1628
|
+
if (disabledSymbols.has(symbolName)) return; // Skip disabled symbols (SL hit)
|
|
1590
1629
|
if (pendingOrders[symbolName]) return;
|
|
1591
1630
|
if (!pm.canEnter(symbolName)) return;
|
|
1592
1631
|
|
|
@@ -1594,19 +1633,22 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1594
1633
|
const orderSide = direction === 'long' ? 0 : 1;
|
|
1595
1634
|
const sideStr = direction === 'long' ? 'LONG' : 'SHORT';
|
|
1596
1635
|
|
|
1636
|
+
// Use per-symbol quantity
|
|
1637
|
+
const symbolQty = contractInfoMap[symbolName].qty;
|
|
1638
|
+
|
|
1597
1639
|
// Calculate risk amount
|
|
1598
1640
|
const kelly = Math.min(0.25, confidence || 0.15);
|
|
1599
1641
|
const riskAmount = Math.round(maxRisk * kelly);
|
|
1600
1642
|
const riskPct = Math.round((riskAmount / maxRisk) * 100);
|
|
1601
1643
|
|
|
1602
1644
|
pendingOrders[symbolName] = true;
|
|
1603
|
-
ui.addLog('entry', `[${symbolName}] ${sideStr} ${
|
|
1645
|
+
ui.addLog('entry', `[${symbolName}] ${sideStr} ${symbolQty}x | risk: $${riskAmount} (${riskPct}%)`);
|
|
1604
1646
|
|
|
1605
1647
|
const orderData = {
|
|
1606
1648
|
accountId: rithmicAccountId,
|
|
1607
1649
|
symbol: symbolName,
|
|
1608
1650
|
exchange: contractInfoMap[symbolName].exchange,
|
|
1609
|
-
size:
|
|
1651
|
+
size: symbolQty,
|
|
1610
1652
|
side: orderSide,
|
|
1611
1653
|
};
|
|
1612
1654
|
|
|
@@ -1747,6 +1789,32 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1747
1789
|
algoLogger.error(ui, 'CONNECTION ERROR', e.message);
|
|
1748
1790
|
}
|
|
1749
1791
|
|
|
1792
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1793
|
+
// TARGET/RISK CHECK - Stop algo when limits reached
|
|
1794
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1795
|
+
const checkTargetRisk = () => {
|
|
1796
|
+
if (!running) return;
|
|
1797
|
+
|
|
1798
|
+
const totalPnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
1799
|
+
|
|
1800
|
+
// Daily target reached - STOP with profit
|
|
1801
|
+
if (totalPnl >= dailyTarget) {
|
|
1802
|
+
stopReason = 'target';
|
|
1803
|
+
running = false;
|
|
1804
|
+
algoLogger.info(ui, 'TARGET REACHED', `+$${totalPnl.toFixed(2)} >= $${dailyTarget}`);
|
|
1805
|
+
ui.addLog('success', `████ DAILY TARGET REACHED: +$${totalPnl.toFixed(2)} ████`);
|
|
1806
|
+
emergencyStopAll(); // Close all positions
|
|
1807
|
+
}
|
|
1808
|
+
// Max risk reached - STOP to protect capital
|
|
1809
|
+
else if (totalPnl <= -maxRisk) {
|
|
1810
|
+
stopReason = 'risk';
|
|
1811
|
+
running = false;
|
|
1812
|
+
algoLogger.info(ui, 'MAX RISK HIT', `-$${Math.abs(totalPnl).toFixed(2)} <= -$${maxRisk}`);
|
|
1813
|
+
ui.addLog('error', `████ MAX RISK REACHED: -$${Math.abs(totalPnl).toFixed(2)} ████`);
|
|
1814
|
+
emergencyStopAll(); // Close all positions
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1750
1818
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1751
1819
|
// REAL-TIME P&L VIA WEBSOCKET (same as launchAlgo)
|
|
1752
1820
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1774,10 +1842,14 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1774
1842
|
}
|
|
1775
1843
|
|
|
1776
1844
|
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
1845
|
+
|
|
1846
|
+
// Check target/risk on every P&L update
|
|
1847
|
+
checkTargetRisk();
|
|
1848
|
+
|
|
1777
1849
|
ui.render(stats);
|
|
1778
1850
|
});
|
|
1779
1851
|
|
|
1780
|
-
// Position-level updates (for Open P&L)
|
|
1852
|
+
// Position-level updates (for Open P&L per symbol)
|
|
1781
1853
|
service.on('positionUpdate', (pos) => {
|
|
1782
1854
|
if (!pos || pos.accountId !== rithmicAccountId) return;
|
|
1783
1855
|
|
|
@@ -1787,14 +1859,20 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1787
1859
|
if (posSymbol === baseSymbol || posSymbol === symbolName || symbolName.startsWith(posSymbol)) {
|
|
1788
1860
|
stats.symbolStats[symbolName].position = pos.quantity || 0;
|
|
1789
1861
|
|
|
1790
|
-
// Update Open P&L
|
|
1862
|
+
// Update Open P&L for this symbol
|
|
1791
1863
|
if (pos.openPnl !== undefined && pos.openPnl !== null) {
|
|
1792
|
-
stats.openPnl = pos.openPnl;
|
|
1864
|
+
stats.symbolStats[symbolName].openPnl = pos.openPnl;
|
|
1865
|
+
|
|
1866
|
+
// Calculate total Open P&L from all symbols
|
|
1867
|
+
stats.openPnl = Object.values(stats.symbolStats).reduce((sum, s) => sum + (s.openPnl || 0), 0);
|
|
1793
1868
|
stats.pnl = (stats.openPnl || 0) + (stats.closedPnl || 0);
|
|
1794
1869
|
|
|
1795
1870
|
if (stats.balance !== null) {
|
|
1796
1871
|
stats.netLiquidation = stats.balance + stats.openPnl;
|
|
1797
1872
|
}
|
|
1873
|
+
|
|
1874
|
+
// Check target/risk on Open P&L changes too
|
|
1875
|
+
checkTargetRisk();
|
|
1798
1876
|
}
|
|
1799
1877
|
break;
|
|
1800
1878
|
}
|