hedgequantx 2.6.126 → 2.6.127
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 +18 -390
package/package.json
CHANGED
|
@@ -78,32 +78,21 @@ 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 symbol
|
|
82
|
+
const contract = await selectSymbol(accountService, selectedAccount);
|
|
83
|
+
if (!contract) return;
|
|
84
84
|
|
|
85
|
-
// Configure algo
|
|
86
|
-
const config = await configureAlgo(selectedAccount,
|
|
85
|
+
// Configure algo
|
|
86
|
+
const config = await configureAlgo(selectedAccount, contract);
|
|
87
87
|
if (!config) return;
|
|
88
88
|
|
|
89
|
-
|
|
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
|
-
}
|
|
89
|
+
await launchAlgo(accountService, selectedAccount, contract, config);
|
|
97
90
|
};
|
|
98
91
|
|
|
99
|
-
// Maximum number of symbols to trade simultaneously
|
|
100
|
-
const MAX_SYMBOLS = 5;
|
|
101
|
-
|
|
102
92
|
/**
|
|
103
93
|
* Symbol selection - sorted with popular indices first
|
|
104
|
-
* Allows selecting multiple symbols (up to MAX_SYMBOLS)
|
|
105
94
|
*/
|
|
106
|
-
const
|
|
95
|
+
const selectSymbol = async (service, account) => {
|
|
107
96
|
const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
|
|
108
97
|
|
|
109
98
|
const contractsResult = await service.getContracts();
|
|
@@ -137,102 +126,28 @@ const selectSymbols = async (service, account) => {
|
|
|
137
126
|
|
|
138
127
|
spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
|
|
139
128
|
|
|
140
|
-
//
|
|
141
|
-
const
|
|
129
|
+
// Display sorted contracts from API (uniform format: NAME - DESCRIPTION)
|
|
130
|
+
const options = contracts.map(c => {
|
|
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
|
+
});
|
|
142
136
|
|
|
143
|
-
|
|
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
|
-
}
|
|
137
|
+
options.push({ label: chalk.gray('< BACK'), value: 'back' });
|
|
201
138
|
|
|
202
|
-
|
|
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;
|
|
139
|
+
const contract = await prompts.selectOption(chalk.yellow('SELECT SYMBOL:'), options);
|
|
140
|
+
return contract === 'back' || contract === null ? null : contract;
|
|
211
141
|
};
|
|
212
142
|
|
|
213
143
|
/**
|
|
214
144
|
* Configure algo
|
|
215
|
-
* @param {Object} account - Trading account
|
|
216
|
-
* @param {Array|Object} contractsOrContract - Single contract or array of contracts
|
|
217
145
|
*/
|
|
218
|
-
const configureAlgo = async (account,
|
|
219
|
-
// Normalize to array
|
|
220
|
-
const contractList = Array.isArray(contractsOrContract) ? contractsOrContract : [contractsOrContract];
|
|
221
|
-
|
|
146
|
+
const configureAlgo = async (account, contract) => {
|
|
222
147
|
console.log();
|
|
223
148
|
console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
|
|
224
149
|
console.log();
|
|
225
150
|
|
|
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
|
-
|
|
236
151
|
const contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
|
|
237
152
|
if (contracts === null) return null;
|
|
238
153
|
|
|
@@ -1368,291 +1283,4 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
1368
1283
|
await prompts.waitForEnter();
|
|
1369
1284
|
};
|
|
1370
1285
|
|
|
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
|
-
|
|
1658
1286
|
module.exports = { oneAccountMenu };
|