hedgequantx 2.6.124 → 2.6.126
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
|
@@ -78,21 +78,32 @@ const oneAccountMenu = async (service) => {
|
|
|
78
78
|
// Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
|
|
79
79
|
const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
|
|
80
80
|
|
|
81
|
-
// Select
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
81
|
+
// Select symbols (up to MAX_SYMBOLS)
|
|
82
|
+
const contracts = await selectSymbols(accountService, selectedAccount);
|
|
83
|
+
if (!contracts || contracts.length === 0) return;
|
|
84
84
|
|
|
85
|
-
// Configure algo
|
|
86
|
-
const config = await configureAlgo(selectedAccount,
|
|
85
|
+
// Configure algo (shared config for all symbols)
|
|
86
|
+
const config = await configureAlgo(selectedAccount, contracts);
|
|
87
87
|
if (!config) return;
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
// Launch algo for all selected symbols
|
|
90
|
+
if (contracts.length === 1) {
|
|
91
|
+
// Single symbol - use original launchAlgo
|
|
92
|
+
await launchAlgo(accountService, selectedAccount, contracts[0], config);
|
|
93
|
+
} else {
|
|
94
|
+
// Multiple symbols - launch parallel trading
|
|
95
|
+
await launchMultiAlgo(accountService, selectedAccount, contracts, config);
|
|
96
|
+
}
|
|
90
97
|
};
|
|
91
98
|
|
|
99
|
+
// Maximum number of symbols to trade simultaneously
|
|
100
|
+
const MAX_SYMBOLS = 5;
|
|
101
|
+
|
|
92
102
|
/**
|
|
93
103
|
* Symbol selection - sorted with popular indices first
|
|
104
|
+
* Allows selecting multiple symbols (up to MAX_SYMBOLS)
|
|
94
105
|
*/
|
|
95
|
-
const
|
|
106
|
+
const selectSymbols = async (service, account) => {
|
|
96
107
|
const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
|
|
97
108
|
|
|
98
109
|
const contractsResult = await service.getContracts();
|
|
@@ -126,28 +137,102 @@ const selectSymbol = async (service, account) => {
|
|
|
126
137
|
|
|
127
138
|
spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
|
|
128
139
|
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
const name = c.name || c.symbol || c.baseSymbol;
|
|
132
|
-
const desc = c.description || '';
|
|
133
|
-
const label = desc ? `${name} - ${desc}` : name;
|
|
134
|
-
return { label, value: c };
|
|
135
|
-
});
|
|
140
|
+
// Multi-symbol selection loop
|
|
141
|
+
const selectedContracts = [];
|
|
136
142
|
|
|
137
|
-
|
|
143
|
+
while (selectedContracts.length < MAX_SYMBOLS) {
|
|
144
|
+
console.log();
|
|
145
|
+
if (selectedContracts.length > 0) {
|
|
146
|
+
console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_SYMBOLS}):`));
|
|
147
|
+
selectedContracts.forEach((c, i) => {
|
|
148
|
+
const name = c.name || c.symbol || c.baseSymbol;
|
|
149
|
+
console.log(chalk.green(` ${i + 1}. ${name}`));
|
|
150
|
+
});
|
|
151
|
+
console.log();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Filter out already selected contracts
|
|
155
|
+
const availableContracts = contracts.filter(c => {
|
|
156
|
+
const cId = c.id || c.symbol || c.name;
|
|
157
|
+
return !selectedContracts.some(sc => (sc.id || sc.symbol || sc.name) === cId);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Build options
|
|
161
|
+
const options = availableContracts.map(c => {
|
|
162
|
+
const name = c.name || c.symbol || c.baseSymbol;
|
|
163
|
+
const desc = c.description || '';
|
|
164
|
+
const label = desc ? `${name} - ${desc}` : name;
|
|
165
|
+
return { label, value: c };
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Add done/back options
|
|
169
|
+
if (selectedContracts.length > 0) {
|
|
170
|
+
options.unshift({ label: chalk.green(`✓ DONE (trade ${selectedContracts.length} symbol${selectedContracts.length > 1 ? 's' : ''})`), value: 'done' });
|
|
171
|
+
}
|
|
172
|
+
options.push({ label: chalk.gray('< BACK'), value: 'back' });
|
|
173
|
+
|
|
174
|
+
const promptText = selectedContracts.length === 0
|
|
175
|
+
? chalk.yellow(`SELECT SYMBOL (1-${MAX_SYMBOLS}):`)
|
|
176
|
+
: chalk.yellow(`ADD SYMBOL (${selectedContracts.length + 1}/${MAX_SYMBOLS}) OR DONE:`);
|
|
177
|
+
|
|
178
|
+
const choice = await prompts.selectOption(promptText, options);
|
|
179
|
+
|
|
180
|
+
if (choice === 'back' || choice === null) {
|
|
181
|
+
if (selectedContracts.length === 0) {
|
|
182
|
+
return null; // Go back
|
|
183
|
+
}
|
|
184
|
+
// Remove last selected
|
|
185
|
+
selectedContracts.pop();
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (choice === 'done') {
|
|
190
|
+
break; // Done selecting
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add selected contract
|
|
194
|
+
selectedContracts.push(choice);
|
|
195
|
+
|
|
196
|
+
if (selectedContracts.length >= MAX_SYMBOLS) {
|
|
197
|
+
console.log(chalk.yellow(` MAX ${MAX_SYMBOLS} SYMBOLS REACHED`));
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
138
201
|
|
|
139
|
-
|
|
140
|
-
|
|
202
|
+
return selectedContracts.length > 0 ? selectedContracts : null;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Legacy single symbol selection (kept for compatibility)
|
|
207
|
+
*/
|
|
208
|
+
const selectSymbol = async (service, account) => {
|
|
209
|
+
const contracts = await selectSymbols(service, account);
|
|
210
|
+
return contracts && contracts.length > 0 ? contracts[0] : null;
|
|
141
211
|
};
|
|
142
212
|
|
|
143
213
|
/**
|
|
144
214
|
* Configure algo
|
|
215
|
+
* @param {Object} account - Trading account
|
|
216
|
+
* @param {Array|Object} contractsOrContract - Single contract or array of contracts
|
|
145
217
|
*/
|
|
146
|
-
const configureAlgo = async (account,
|
|
218
|
+
const configureAlgo = async (account, contractsOrContract) => {
|
|
219
|
+
// Normalize to array
|
|
220
|
+
const contractList = Array.isArray(contractsOrContract) ? contractsOrContract : [contractsOrContract];
|
|
221
|
+
|
|
147
222
|
console.log();
|
|
148
223
|
console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
|
|
149
224
|
console.log();
|
|
150
225
|
|
|
226
|
+
// Show selected symbols
|
|
227
|
+
if (contractList.length > 1) {
|
|
228
|
+
console.log(chalk.white(` TRADING ${contractList.length} SYMBOLS:`));
|
|
229
|
+
contractList.forEach((c, i) => {
|
|
230
|
+
const name = c.name || c.symbol || c.baseSymbol;
|
|
231
|
+
console.log(chalk.yellow(` ${i + 1}. ${name}`));
|
|
232
|
+
});
|
|
233
|
+
console.log();
|
|
234
|
+
}
|
|
235
|
+
|
|
151
236
|
const contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
|
|
152
237
|
if (contracts === null) return null;
|
|
153
238
|
|
|
@@ -1283,4 +1368,291 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
1283
1368
|
await prompts.waitForEnter();
|
|
1284
1369
|
};
|
|
1285
1370
|
|
|
1371
|
+
/**
|
|
1372
|
+
* Launch multi-symbol algo trading
|
|
1373
|
+
* Runs multiple symbols in parallel on the same account
|
|
1374
|
+
*
|
|
1375
|
+
* @param {Object} service - Trading service
|
|
1376
|
+
* @param {Object} account - Trading account
|
|
1377
|
+
* @param {Array} contracts - Array of contracts to trade
|
|
1378
|
+
* @param {Object} config - Algo configuration
|
|
1379
|
+
*/
|
|
1380
|
+
const launchMultiAlgo = async (service, account, contracts, config) => {
|
|
1381
|
+
const { contracts: qty, dailyTarget, maxRisk, showName } = config;
|
|
1382
|
+
|
|
1383
|
+
const accountName = showName
|
|
1384
|
+
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
1385
|
+
: 'HQX *****';
|
|
1386
|
+
const connectionType = account.platform || 'ProjectX';
|
|
1387
|
+
const useFastPath = isRithmicFastPath(service);
|
|
1388
|
+
|
|
1389
|
+
// Build symbol list string
|
|
1390
|
+
const symbolNames = contracts.map(c => c.name || c.symbol || c.baseSymbol);
|
|
1391
|
+
const symbolsStr = symbolNames.join(', ');
|
|
1392
|
+
|
|
1393
|
+
const ui = new AlgoUI({
|
|
1394
|
+
subtitle: `MULTI-SYMBOL (${contracts.length})`,
|
|
1395
|
+
mode: 'one-account'
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
// Shared stats across all symbols
|
|
1399
|
+
const stats = {
|
|
1400
|
+
accountName,
|
|
1401
|
+
symbol: symbolsStr,
|
|
1402
|
+
qty,
|
|
1403
|
+
target: dailyTarget,
|
|
1404
|
+
risk: maxRisk,
|
|
1405
|
+
propfirm: account.propfirm || 'Unknown',
|
|
1406
|
+
platform: connectionType,
|
|
1407
|
+
pnl: null,
|
|
1408
|
+
openPnl: null,
|
|
1409
|
+
closedPnl: null,
|
|
1410
|
+
balance: null,
|
|
1411
|
+
buyingPower: null,
|
|
1412
|
+
margin: null,
|
|
1413
|
+
netLiquidation: null,
|
|
1414
|
+
position: 0,
|
|
1415
|
+
entryPrice: 0,
|
|
1416
|
+
lastPrice: 0,
|
|
1417
|
+
trades: 0,
|
|
1418
|
+
wins: 0,
|
|
1419
|
+
losses: 0,
|
|
1420
|
+
sessionPnl: 0,
|
|
1421
|
+
latency: 0,
|
|
1422
|
+
connected: false,
|
|
1423
|
+
startTime: Date.now(),
|
|
1424
|
+
aiSupervision: config.enableAI || false,
|
|
1425
|
+
aiMode: null,
|
|
1426
|
+
agentCount: 0,
|
|
1427
|
+
fastPath: useFastPath,
|
|
1428
|
+
avgEntryLatency: 0,
|
|
1429
|
+
avgFillLatency: 0,
|
|
1430
|
+
entryLatencies: [],
|
|
1431
|
+
// Multi-symbol specific
|
|
1432
|
+
activeSymbols: contracts.length,
|
|
1433
|
+
symbolStats: {}, // Per-symbol stats
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
// Initialize per-symbol stats
|
|
1437
|
+
contracts.forEach(c => {
|
|
1438
|
+
const name = c.name || c.symbol;
|
|
1439
|
+
stats.symbolStats[name] = {
|
|
1440
|
+
position: 0,
|
|
1441
|
+
trades: 0,
|
|
1442
|
+
wins: 0,
|
|
1443
|
+
losses: 0,
|
|
1444
|
+
pnl: 0,
|
|
1445
|
+
};
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
let running = true;
|
|
1449
|
+
let stopReason = null;
|
|
1450
|
+
|
|
1451
|
+
// Track market feeds and position managers for cleanup
|
|
1452
|
+
const marketFeeds = [];
|
|
1453
|
+
const positionManagers = [];
|
|
1454
|
+
|
|
1455
|
+
ui.render(stats);
|
|
1456
|
+
ui.addLog('info', `MULTI-SYMBOL MODE: ${contracts.length} symbols`);
|
|
1457
|
+
symbolNames.forEach(name => ui.addLog('info', ` → ${name}`));
|
|
1458
|
+
|
|
1459
|
+
// Launch trading for each symbol
|
|
1460
|
+
const symbolPromises = contracts.map(async (contract, index) => {
|
|
1461
|
+
const symbolName = contract.name || contract.symbol;
|
|
1462
|
+
const contractId = contract.id || contract.symbol || contract.name;
|
|
1463
|
+
const tickSize = contract.tickSize ?? null;
|
|
1464
|
+
const tickValue = contract.tickValue ?? null;
|
|
1465
|
+
|
|
1466
|
+
// Initialize strategy for this symbol
|
|
1467
|
+
const strategy = (USE_HFT_STRATEGY && useFastPath) ? hftStrategy : M1;
|
|
1468
|
+
if (tickSize !== null && tickValue !== null) {
|
|
1469
|
+
strategy.initialize(contractId, tickSize, tickValue);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Initialize Position Manager for fast path
|
|
1473
|
+
let positionManager = null;
|
|
1474
|
+
if (useFastPath) {
|
|
1475
|
+
positionManager = new PositionManager(service, strategy);
|
|
1476
|
+
if (tickSize !== null && tickValue !== null) {
|
|
1477
|
+
positionManager.setContractInfo(symbolName, { tickSize, tickValue, contractId });
|
|
1478
|
+
}
|
|
1479
|
+
positionManager.start();
|
|
1480
|
+
positionManagers.push(positionManager);
|
|
1481
|
+
|
|
1482
|
+
// Position manager events
|
|
1483
|
+
positionManager.on('entryFilled', ({ position, fillLatencyMs }) => {
|
|
1484
|
+
stats.entryLatencies.push(fillLatencyMs);
|
|
1485
|
+
stats.avgFillLatency = stats.entryLatencies.reduce((a, b) => a + b, 0) / stats.entryLatencies.length;
|
|
1486
|
+
const side = position.side === 0 ? 'LONG' : 'SHORT';
|
|
1487
|
+
ui.addLog('filled', `[${symbolName}] ${side} ${position.size}x @ ${position.entryPrice} | ${fillLatencyMs}ms`);
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
positionManager.on('exitFilled', ({ exitPrice, pnlTicks, holdDurationMs }) => {
|
|
1491
|
+
const holdSec = (holdDurationMs / 1000).toFixed(1);
|
|
1492
|
+
if (pnlTicks !== null && tickValue !== null) {
|
|
1493
|
+
const pnlDollars = pnlTicks * tickValue;
|
|
1494
|
+
stats.sessionPnl += pnlDollars;
|
|
1495
|
+
stats.symbolStats[symbolName].pnl += pnlDollars;
|
|
1496
|
+
|
|
1497
|
+
if (pnlDollars >= 0) {
|
|
1498
|
+
stats.wins++;
|
|
1499
|
+
stats.symbolStats[symbolName].wins++;
|
|
1500
|
+
ui.addLog('win', `[${symbolName}] +$${pnlDollars.toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
1501
|
+
} else {
|
|
1502
|
+
stats.losses++;
|
|
1503
|
+
stats.symbolStats[symbolName].losses++;
|
|
1504
|
+
ui.addLog('loss', `[${symbolName}] -$${Math.abs(pnlDollars).toFixed(2)} @ ${exitPrice} | ${holdSec}s`);
|
|
1505
|
+
}
|
|
1506
|
+
stats.trades++;
|
|
1507
|
+
stats.symbolStats[symbolName].trades++;
|
|
1508
|
+
}
|
|
1509
|
+
ui.render(stats);
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// Connect market feed for this symbol
|
|
1514
|
+
const MarketFeedClass = useFastPath ? RithmicMarketDataFeed : MarketDataFeed;
|
|
1515
|
+
const marketFeed = new MarketFeedClass(service);
|
|
1516
|
+
marketFeeds.push(marketFeed);
|
|
1517
|
+
|
|
1518
|
+
try {
|
|
1519
|
+
await marketFeed.connect(contractId, account.accountId);
|
|
1520
|
+
ui.addLog('success', `[${symbolName}] Market feed connected`);
|
|
1521
|
+
stats.connected = true;
|
|
1522
|
+
|
|
1523
|
+
// Handle market data ticks
|
|
1524
|
+
marketFeed.on('tick', (tick) => {
|
|
1525
|
+
if (!running) return;
|
|
1526
|
+
|
|
1527
|
+
stats.latency = tick.latency || 0;
|
|
1528
|
+
stats.lastPrice = tick.price || tick.last || 0;
|
|
1529
|
+
|
|
1530
|
+
// Get strategy signal
|
|
1531
|
+
const signal = strategy.onTick ? strategy.onTick(tick) : strategy.analyze?.(tick);
|
|
1532
|
+
|
|
1533
|
+
if (signal && signal.action && signal.action !== 'hold') {
|
|
1534
|
+
if (useFastPath && positionManager) {
|
|
1535
|
+
// Fast path execution
|
|
1536
|
+
if (signal.action === 'buy' || signal.action === 'long') {
|
|
1537
|
+
positionManager.openPosition(symbolName, contract.exchange || 'CME', 0, qty, account.accountId);
|
|
1538
|
+
} else if (signal.action === 'sell' || signal.action === 'short') {
|
|
1539
|
+
positionManager.openPosition(symbolName, contract.exchange || 'CME', 1, qty, account.accountId);
|
|
1540
|
+
} else if (signal.action === 'close' || signal.action === 'exit') {
|
|
1541
|
+
positionManager.closePosition(symbolName);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
ui.render(stats);
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
} catch (err) {
|
|
1550
|
+
ui.addLog('error', `[${symbolName}] Failed to connect: ${err.message}`);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
// Wait for all symbols to initialize
|
|
1555
|
+
await Promise.all(symbolPromises);
|
|
1556
|
+
|
|
1557
|
+
// Keyboard handler for emergency stop
|
|
1558
|
+
let emergencyStopInProgress = false;
|
|
1559
|
+
|
|
1560
|
+
const setupKeyHandler = () => {
|
|
1561
|
+
if (!process.stdin.isTTY) return;
|
|
1562
|
+
readline.emitKeypressEvents(process.stdin);
|
|
1563
|
+
process.stdin.setRawMode(true);
|
|
1564
|
+
process.stdin.resume();
|
|
1565
|
+
|
|
1566
|
+
const onKey = async (str, key) => {
|
|
1567
|
+
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
1568
|
+
if (emergencyStopInProgress) return;
|
|
1569
|
+
emergencyStopInProgress = true;
|
|
1570
|
+
running = false;
|
|
1571
|
+
stopReason = 'manual';
|
|
1572
|
+
|
|
1573
|
+
ui.addLog('warning', '████ EMERGENCY STOP - CLOSING ALL POSITIONS ████');
|
|
1574
|
+
|
|
1575
|
+
// Close all positions on all symbols
|
|
1576
|
+
for (const pm of positionManagers) {
|
|
1577
|
+
try {
|
|
1578
|
+
pm.closeAllPositions();
|
|
1579
|
+
} catch (e) {}
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
ui.addLog('info', 'All positions closed');
|
|
1583
|
+
ui.render(stats);
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
process.stdin.on('keypress', onKey);
|
|
1587
|
+
return () => {
|
|
1588
|
+
process.stdin.removeListener('keypress', onKey);
|
|
1589
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1590
|
+
};
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
const cleanupKeys = setupKeyHandler();
|
|
1594
|
+
|
|
1595
|
+
// Wait for stop
|
|
1596
|
+
await new Promise(resolve => {
|
|
1597
|
+
const check = setInterval(() => {
|
|
1598
|
+
if (!running) {
|
|
1599
|
+
clearInterval(check);
|
|
1600
|
+
resolve();
|
|
1601
|
+
}
|
|
1602
|
+
}, 100);
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
// Cleanup
|
|
1606
|
+
for (const pm of positionManagers) {
|
|
1607
|
+
try { pm.stop(); } catch {}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
for (const feed of marketFeeds) {
|
|
1611
|
+
try { await feed.disconnect(); } catch {}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
try { if (cleanupKeys) cleanupKeys(); } catch {}
|
|
1615
|
+
try { ui.cleanup(); } catch {}
|
|
1616
|
+
|
|
1617
|
+
try {
|
|
1618
|
+
if (process.stdin.isTTY) {
|
|
1619
|
+
process.stdin.setRawMode(false);
|
|
1620
|
+
}
|
|
1621
|
+
process.stdin.resume();
|
|
1622
|
+
} catch {}
|
|
1623
|
+
|
|
1624
|
+
// Duration
|
|
1625
|
+
const durationMs = Date.now() - stats.startTime;
|
|
1626
|
+
const hours = Math.floor(durationMs / 3600000);
|
|
1627
|
+
const minutes = Math.floor((durationMs % 3600000) / 60000);
|
|
1628
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
1629
|
+
stats.duration = hours > 0
|
|
1630
|
+
? `${hours}h ${minutes}m ${seconds}s`
|
|
1631
|
+
: minutes > 0
|
|
1632
|
+
? `${minutes}m ${seconds}s`
|
|
1633
|
+
: `${seconds}s`;
|
|
1634
|
+
|
|
1635
|
+
// Summary with per-symbol breakdown
|
|
1636
|
+
console.log();
|
|
1637
|
+
console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
|
|
1638
|
+
console.log(chalk.cyan(' MULTI-SYMBOL SESSION SUMMARY'));
|
|
1639
|
+
console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
|
|
1640
|
+
console.log();
|
|
1641
|
+
|
|
1642
|
+
for (const [symbol, symStats] of Object.entries(stats.symbolStats)) {
|
|
1643
|
+
const winRate = symStats.trades > 0 ? ((symStats.wins / symStats.trades) * 100).toFixed(0) : 0;
|
|
1644
|
+
const pnlColor = symStats.pnl >= 0 ? chalk.green : chalk.red;
|
|
1645
|
+
console.log(chalk.white(` ${symbol}:`));
|
|
1646
|
+
console.log(` Trades: ${symStats.trades} | WR: ${winRate}% | P&L: ${pnlColor('$' + symStats.pnl.toFixed(2))}`);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
console.log();
|
|
1650
|
+
console.log(chalk.white(` TOTAL: ${stats.trades} trades | ${stats.wins}W/${stats.losses}L | Session P&L: ${stats.sessionPnl >= 0 ? chalk.green('$' + stats.sessionPnl.toFixed(2)) : chalk.red('$' + stats.sessionPnl.toFixed(2))}`));
|
|
1651
|
+
console.log(chalk.white(` Duration: ${stats.duration}`));
|
|
1652
|
+
console.log();
|
|
1653
|
+
console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
|
|
1654
|
+
|
|
1655
|
+
await prompts.waitForEnter();
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1286
1658
|
module.exports = { oneAccountMenu };
|
|
@@ -529,8 +529,10 @@ const submitCallback = async (callbackUrl, provider = 'anthropic') => {
|
|
|
529
529
|
const config = providerConfig[provider] || providerConfig.anthropic;
|
|
530
530
|
|
|
531
531
|
// Submit to the provider's OAuth callback port directly
|
|
532
|
+
// Pass ALL query parameters from the callback URL, not just code and state
|
|
533
|
+
// Some providers (like Gemini) require additional params like scope, authuser, etc.
|
|
532
534
|
return new Promise((resolve, reject) => {
|
|
533
|
-
const callbackPath = `${config.path}
|
|
535
|
+
const callbackPath = `${config.path}?${url.searchParams.toString()}`;
|
|
534
536
|
|
|
535
537
|
const req = http.request({
|
|
536
538
|
hostname: '127.0.0.1',
|