hedgequantx 1.1.1 → 1.2.31
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/README.md +128 -136
- package/bin/cli.js +28 -2076
- package/package.json +3 -3
- package/src/app.js +550 -0
- package/src/config/index.js +16 -2
- package/src/config/propfirms.js +324 -12
- package/src/pages/accounts.js +115 -0
- package/src/pages/algo.js +538 -0
- package/src/pages/index.js +13 -2
- package/src/pages/orders.js +114 -0
- package/src/pages/positions.js +115 -0
- package/src/pages/stats.js +212 -3
- package/src/pages/user.js +92 -0
- package/src/security/encryption.js +168 -0
- package/src/security/index.js +61 -0
- package/src/security/rateLimit.js +155 -0
- package/src/security/validation.js +253 -0
- package/src/services/hqx-server.js +34 -17
- package/src/services/index.js +2 -1
- package/src/services/projectx.js +383 -35
- package/src/services/session.js +150 -38
- package/src/ui/index.js +4 -1
- package/src/ui/menu.js +154 -0
- package/src/services/local-storage.js +0 -309
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Algo Trading Page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const ora = require('ora');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const readline = require('readline');
|
|
9
|
+
|
|
10
|
+
const { connections } = require('../services');
|
|
11
|
+
const { HQXServerService } = require('../services/hqx-server');
|
|
12
|
+
const { FUTURES_SYMBOLS } = require('../config');
|
|
13
|
+
const { getDevice, getSeparator } = require('../ui');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Algo Trading Menu
|
|
17
|
+
*/
|
|
18
|
+
const algoTradingMenu = async (service) => {
|
|
19
|
+
const device = getDevice();
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(chalk.gray(getSeparator()));
|
|
22
|
+
console.log(chalk.magenta.bold(' Algo-Trading'));
|
|
23
|
+
console.log(chalk.gray(getSeparator()));
|
|
24
|
+
console.log();
|
|
25
|
+
|
|
26
|
+
const { action } = await inquirer.prompt([
|
|
27
|
+
{
|
|
28
|
+
type: 'list',
|
|
29
|
+
name: 'action',
|
|
30
|
+
message: chalk.white.bold('Select Mode:'),
|
|
31
|
+
choices: [
|
|
32
|
+
{ name: chalk.cyan('One Account'), value: 'one_account' },
|
|
33
|
+
{ name: chalk.gray('Copy Trading (Coming Soon)'), value: 'copy_trading', disabled: 'Coming Soon' },
|
|
34
|
+
new inquirer.Separator(),
|
|
35
|
+
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
36
|
+
],
|
|
37
|
+
pageSize: 10,
|
|
38
|
+
loop: false
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
switch (action) {
|
|
43
|
+
case 'one_account':
|
|
44
|
+
await oneAccountMenu(service);
|
|
45
|
+
break;
|
|
46
|
+
case 'copy_trading':
|
|
47
|
+
// Disabled - Coming Soon
|
|
48
|
+
break;
|
|
49
|
+
case 'back':
|
|
50
|
+
return 'back';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return action;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One Account Menu - Select active account
|
|
58
|
+
*/
|
|
59
|
+
const oneAccountMenu = async (service) => {
|
|
60
|
+
const spinner = ora('Fetching active accounts...').start();
|
|
61
|
+
|
|
62
|
+
const result = await service.getTradingAccounts();
|
|
63
|
+
|
|
64
|
+
if (!result.success || !result.accounts || result.accounts.length === 0) {
|
|
65
|
+
spinner.fail('No accounts found');
|
|
66
|
+
console.log(chalk.yellow(' You need at least one trading account.'));
|
|
67
|
+
console.log();
|
|
68
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Filter only active accounts (status === 0)
|
|
73
|
+
const activeAccounts = result.accounts.filter(acc => acc.status === 0);
|
|
74
|
+
|
|
75
|
+
if (activeAccounts.length === 0) {
|
|
76
|
+
spinner.fail('No active accounts found');
|
|
77
|
+
console.log(chalk.yellow(' You need at least one active trading account (status: Active).'));
|
|
78
|
+
console.log();
|
|
79
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
const accountChoices = activeAccounts.map(account => ({
|
|
87
|
+
name: chalk.cyan(`${account.accountName || account.name || 'Account #' + account.accountId} - Balance: $${account.balance.toLocaleString()}`),
|
|
88
|
+
value: account
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
accountChoices.push(new inquirer.Separator());
|
|
92
|
+
accountChoices.push({ name: chalk.yellow('< Back'), value: 'back' });
|
|
93
|
+
|
|
94
|
+
const { selectedAccount } = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'list',
|
|
97
|
+
name: 'selectedAccount',
|
|
98
|
+
message: chalk.white.bold('Select Account:'),
|
|
99
|
+
choices: accountChoices,
|
|
100
|
+
pageSize: 15,
|
|
101
|
+
loop: false
|
|
102
|
+
}
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
if (selectedAccount === 'back') {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check market status
|
|
110
|
+
console.log();
|
|
111
|
+
const marketSpinner = ora('Checking market status...').start();
|
|
112
|
+
|
|
113
|
+
const marketHours = service.checkMarketHours();
|
|
114
|
+
const marketStatus = await service.getMarketStatus(selectedAccount.accountId);
|
|
115
|
+
|
|
116
|
+
if (!marketHours.isOpen) {
|
|
117
|
+
marketSpinner.fail('Market is CLOSED');
|
|
118
|
+
console.log();
|
|
119
|
+
console.log(chalk.red.bold(' [X] ' + marketHours.message));
|
|
120
|
+
console.log();
|
|
121
|
+
console.log(chalk.gray(' Futures markets (CME) trading hours:'));
|
|
122
|
+
console.log(chalk.gray(' Sunday 5:00 PM CT - Friday 4:00 PM CT'));
|
|
123
|
+
console.log(chalk.gray(' Daily maintenance: 4:00 PM - 5:00 PM CT'));
|
|
124
|
+
console.log();
|
|
125
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (marketStatus.success && !marketStatus.isOpen) {
|
|
130
|
+
marketSpinner.fail('Cannot trade on this account');
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(chalk.red.bold(' [X] ' + marketStatus.message));
|
|
133
|
+
console.log();
|
|
134
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
marketSpinner.succeed('Market is OPEN - Ready to trade!');
|
|
139
|
+
|
|
140
|
+
await selectSymbolMenu(service, selectedAccount);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Symbol Selection Menu
|
|
145
|
+
*/
|
|
146
|
+
const selectSymbolMenu = async (service, account) => {
|
|
147
|
+
const device = getDevice();
|
|
148
|
+
const accountName = account.accountName || account.name || 'Account #' + account.accountId;
|
|
149
|
+
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(chalk.gray(getSeparator()));
|
|
152
|
+
console.log(chalk.cyan.bold(` Account: ${accountName}`));
|
|
153
|
+
console.log(chalk.gray(getSeparator()));
|
|
154
|
+
console.log();
|
|
155
|
+
|
|
156
|
+
const symbolChoices = FUTURES_SYMBOLS.map(symbol => ({
|
|
157
|
+
name: chalk.cyan(device.isMobile ? symbol.value : symbol.name),
|
|
158
|
+
value: symbol
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
symbolChoices.push(new inquirer.Separator());
|
|
162
|
+
symbolChoices.push({ name: chalk.yellow('< Back'), value: 'back' });
|
|
163
|
+
|
|
164
|
+
const { selectedSymbol } = await inquirer.prompt([
|
|
165
|
+
{
|
|
166
|
+
type: 'list',
|
|
167
|
+
name: 'selectedSymbol',
|
|
168
|
+
message: chalk.white.bold('Select Symbol:'),
|
|
169
|
+
choices: symbolChoices,
|
|
170
|
+
pageSize: 15,
|
|
171
|
+
loop: false
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
if (selectedSymbol === 'back') {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Search contract via Gateway API
|
|
180
|
+
const spinner = ora(`Searching for ${selectedSymbol.value} contract...`).start();
|
|
181
|
+
const contractResult = await service.searchContracts(selectedSymbol.searchText, false);
|
|
182
|
+
|
|
183
|
+
let contract = null;
|
|
184
|
+
if (contractResult.success && contractResult.contracts && contractResult.contracts.length > 0) {
|
|
185
|
+
contract = contractResult.contracts.find(c => c.activeContract) || contractResult.contracts[0];
|
|
186
|
+
spinner.succeed(`Found: ${contract.name || selectedSymbol.value}`);
|
|
187
|
+
if (contract.tickSize && contract.tickValue) {
|
|
188
|
+
console.log(chalk.gray(` Tick Size: ${contract.tickSize} | Tick Value: $${contract.tickValue}`));
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
spinner.warn(`Using ${selectedSymbol.value} (contract details unavailable)`);
|
|
192
|
+
contract = {
|
|
193
|
+
id: selectedSymbol.value,
|
|
194
|
+
name: selectedSymbol.name,
|
|
195
|
+
symbol: selectedSymbol.value
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log();
|
|
200
|
+
|
|
201
|
+
// Number of contracts
|
|
202
|
+
const { contracts } = await inquirer.prompt([
|
|
203
|
+
{
|
|
204
|
+
type: 'input',
|
|
205
|
+
name: 'contracts',
|
|
206
|
+
message: chalk.white.bold('Number of Contracts:'),
|
|
207
|
+
default: '1',
|
|
208
|
+
validate: (input) => {
|
|
209
|
+
const num = parseInt(input);
|
|
210
|
+
if (isNaN(num) || num <= 0 || num > 100) {
|
|
211
|
+
return 'Please enter a valid number between 1 and 100';
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
filter: (input) => parseInt(input)
|
|
216
|
+
}
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
// Confirmation
|
|
220
|
+
console.log();
|
|
221
|
+
console.log(chalk.gray(getSeparator()));
|
|
222
|
+
console.log(chalk.white.bold(' Algo Configuration:'));
|
|
223
|
+
console.log(chalk.gray(getSeparator()));
|
|
224
|
+
console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
|
|
225
|
+
console.log(chalk.white(` Symbol: ${chalk.cyan(contract.name || selectedSymbol.value)}`));
|
|
226
|
+
console.log(chalk.white(` Contracts: ${chalk.cyan(contracts)}`));
|
|
227
|
+
console.log(chalk.gray(getSeparator()));
|
|
228
|
+
console.log();
|
|
229
|
+
|
|
230
|
+
const { launch } = await inquirer.prompt([
|
|
231
|
+
{
|
|
232
|
+
type: 'list',
|
|
233
|
+
name: 'launch',
|
|
234
|
+
message: chalk.white.bold('Ready to launch?'),
|
|
235
|
+
choices: [
|
|
236
|
+
{ name: chalk.green.bold('[>] Launch Algo'), value: 'launch' },
|
|
237
|
+
{ name: chalk.yellow('< Back'), value: 'back' }
|
|
238
|
+
],
|
|
239
|
+
loop: false
|
|
240
|
+
}
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
if (launch === 'back') {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await launchAlgo(service, account, contract, contracts);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Launch Algo with HQX Server Connection
|
|
252
|
+
*/
|
|
253
|
+
const launchAlgo = async (service, account, contract, numContracts) => {
|
|
254
|
+
const accountName = account.accountName || account.name || 'Account #' + account.accountId;
|
|
255
|
+
const symbolName = contract.name || contract.symbol || contract.id;
|
|
256
|
+
const symbol = contract.symbol || contract.id;
|
|
257
|
+
|
|
258
|
+
console.log();
|
|
259
|
+
console.log(chalk.green.bold(' [>] Launching HQX Algo...'));
|
|
260
|
+
console.log();
|
|
261
|
+
|
|
262
|
+
// Initialize HQX Server connection
|
|
263
|
+
const hqxServer = new HQXServerService();
|
|
264
|
+
let hqxConnected = false;
|
|
265
|
+
let algoRunning = false;
|
|
266
|
+
|
|
267
|
+
// Activity logs
|
|
268
|
+
const logs = [];
|
|
269
|
+
const MAX_LOGS = 12;
|
|
270
|
+
|
|
271
|
+
// Stats
|
|
272
|
+
let stats = {
|
|
273
|
+
trades: 0,
|
|
274
|
+
wins: 0,
|
|
275
|
+
losses: 0,
|
|
276
|
+
pnl: 0,
|
|
277
|
+
signals: 0,
|
|
278
|
+
winRate: '0.0'
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const addLog = (type, message) => {
|
|
282
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
283
|
+
logs.push({ timestamp, type, message });
|
|
284
|
+
if (logs.length > MAX_LOGS) logs.shift();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const clearScreen = () => {
|
|
288
|
+
console.clear();
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const displayUI = () => {
|
|
292
|
+
clearScreen();
|
|
293
|
+
console.log();
|
|
294
|
+
console.log(chalk.gray(getSeparator()));
|
|
295
|
+
console.log(chalk.cyan.bold(' HQX Ultra-Scalping Algo'));
|
|
296
|
+
console.log(chalk.gray(getSeparator()));
|
|
297
|
+
console.log(chalk.white(` Status: ${algoRunning ? chalk.green('RUNNING') : chalk.yellow('CONNECTING...')}`));
|
|
298
|
+
console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
|
|
299
|
+
console.log(chalk.white(` Symbol: ${chalk.cyan(symbolName)}`));
|
|
300
|
+
console.log(chalk.white(` Contracts: ${chalk.cyan(numContracts)}`));
|
|
301
|
+
console.log(chalk.white(` Mode: ${hqxConnected ? chalk.green('LIVE') : chalk.yellow('OFFLINE')}`));
|
|
302
|
+
console.log(chalk.gray(getSeparator()));
|
|
303
|
+
|
|
304
|
+
// Stats bar
|
|
305
|
+
console.log();
|
|
306
|
+
console.log(chalk.white(' Stats: ') +
|
|
307
|
+
chalk.gray('Trades: ') + chalk.cyan(stats.trades) +
|
|
308
|
+
chalk.gray(' | Wins: ') + chalk.green(stats.wins) +
|
|
309
|
+
chalk.gray(' | Losses: ') + chalk.red(stats.losses) +
|
|
310
|
+
chalk.gray(' | Win Rate: ') + chalk.yellow(stats.winRate + '%') +
|
|
311
|
+
chalk.gray(' | P&L: ') + (stats.pnl >= 0 ? chalk.green('$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2)))
|
|
312
|
+
);
|
|
313
|
+
console.log();
|
|
314
|
+
|
|
315
|
+
// Activity logs
|
|
316
|
+
console.log(chalk.gray(getSeparator()));
|
|
317
|
+
console.log(chalk.white.bold(' Activity Log'));
|
|
318
|
+
console.log(chalk.gray(getSeparator()));
|
|
319
|
+
|
|
320
|
+
const typeColors = {
|
|
321
|
+
info: chalk.cyan,
|
|
322
|
+
success: chalk.green,
|
|
323
|
+
signal: chalk.yellow.bold,
|
|
324
|
+
trade: chalk.green.bold,
|
|
325
|
+
error: chalk.red,
|
|
326
|
+
warning: chalk.yellow
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (logs.length === 0) {
|
|
330
|
+
console.log(chalk.gray(' Waiting for activity...'));
|
|
331
|
+
} else {
|
|
332
|
+
logs.forEach(log => {
|
|
333
|
+
const color = typeColors[log.type] || chalk.white;
|
|
334
|
+
const icon = log.type === 'signal' ? '[*]' :
|
|
335
|
+
log.type === 'trade' ? '[>]' :
|
|
336
|
+
log.type === 'error' ? '[X]' :
|
|
337
|
+
log.type === 'success' ? '[OK]' : '[.]';
|
|
338
|
+
console.log(chalk.gray(` [${log.timestamp}]`) + ' ' + color(`${icon} ${log.message}`));
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
console.log(chalk.gray(getSeparator()));
|
|
343
|
+
console.log();
|
|
344
|
+
console.log(chalk.yellow(' Press X to stop algo...'));
|
|
345
|
+
console.log();
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Connect to HQX Server
|
|
349
|
+
const spinner = ora('Authenticating with HQX Server...').start();
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
// Authenticate
|
|
353
|
+
const authResult = await hqxServer.authenticate(account.accountId.toString(), account.propfirm || 'projectx');
|
|
354
|
+
|
|
355
|
+
if (!authResult.success) {
|
|
356
|
+
spinner.fail('Authentication failed: ' + (authResult.error || 'Unknown error'));
|
|
357
|
+
addLog('error', 'Authentication failed');
|
|
358
|
+
|
|
359
|
+
// Fallback to offline mode
|
|
360
|
+
console.log(chalk.yellow(' Running in offline demo mode...'));
|
|
361
|
+
console.log();
|
|
362
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
spinner.text = 'Connecting to WebSocket...';
|
|
367
|
+
|
|
368
|
+
// Connect WebSocket
|
|
369
|
+
const connectResult = await hqxServer.connect();
|
|
370
|
+
|
|
371
|
+
if (connectResult.success) {
|
|
372
|
+
spinner.succeed('Connected to HQX Server');
|
|
373
|
+
hqxConnected = true;
|
|
374
|
+
} else {
|
|
375
|
+
throw new Error('WebSocket connection failed');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
} catch (error) {
|
|
379
|
+
spinner.warn('HQX Server unavailable - Running in offline mode');
|
|
380
|
+
hqxConnected = false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Setup event handlers
|
|
384
|
+
hqxServer.on('log', (data) => {
|
|
385
|
+
addLog(data.type || 'info', data.message);
|
|
386
|
+
displayUI();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
hqxServer.on('signal', (data) => {
|
|
390
|
+
stats.signals++;
|
|
391
|
+
const side = data.side === 'long' ? 'BUY' : 'SELL';
|
|
392
|
+
addLog('signal', `${side} Signal @ ${data.entry?.toFixed(2) || 'N/A'} | SL: ${data.stop?.toFixed(2) || 'N/A'} | TP: ${data.target?.toFixed(2) || 'N/A'}`);
|
|
393
|
+
displayUI();
|
|
394
|
+
|
|
395
|
+
// Execute order via PropFirm API if connected
|
|
396
|
+
if (hqxConnected && service) {
|
|
397
|
+
executeSignal(service, account, contract, numContracts, data);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
hqxServer.on('trade', (data) => {
|
|
402
|
+
stats.trades++;
|
|
403
|
+
stats.pnl += data.pnl || 0;
|
|
404
|
+
if (data.pnl > 0) {
|
|
405
|
+
stats.wins++;
|
|
406
|
+
addLog('trade', `Closed +$${data.pnl.toFixed(2)} (${data.reason || 'take_profit'})`);
|
|
407
|
+
} else {
|
|
408
|
+
stats.losses++;
|
|
409
|
+
addLog('trade', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
|
|
410
|
+
}
|
|
411
|
+
stats.winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : '0.0';
|
|
412
|
+
displayUI();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
hqxServer.on('stats', (data) => {
|
|
416
|
+
stats = { ...stats, ...data };
|
|
417
|
+
displayUI();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
hqxServer.on('error', (data) => {
|
|
421
|
+
addLog('error', data.message || 'Unknown error');
|
|
422
|
+
displayUI();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
hqxServer.on('disconnected', () => {
|
|
426
|
+
hqxConnected = false;
|
|
427
|
+
addLog('warning', 'Disconnected from HQX Server');
|
|
428
|
+
displayUI();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Start algo
|
|
432
|
+
if (hqxConnected) {
|
|
433
|
+
addLog('info', 'Starting HQX Ultra-Scalping...');
|
|
434
|
+
hqxServer.startAlgo({
|
|
435
|
+
accountId: account.accountId,
|
|
436
|
+
contractId: contract.id || contract.contractId,
|
|
437
|
+
symbol: symbol,
|
|
438
|
+
contracts: numContracts,
|
|
439
|
+
dailyTarget: 500, // Default daily target
|
|
440
|
+
maxRisk: 200, // Default max risk
|
|
441
|
+
propfirm: account.propfirm || 'projectx',
|
|
442
|
+
propfirmToken: service.getToken ? service.getToken() : null
|
|
443
|
+
});
|
|
444
|
+
algoRunning = true;
|
|
445
|
+
} else {
|
|
446
|
+
addLog('warning', 'Running in offline demo mode');
|
|
447
|
+
addLog('info', 'No real trades will be executed');
|
|
448
|
+
algoRunning = true;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
displayUI();
|
|
452
|
+
|
|
453
|
+
// Wait for X key to stop
|
|
454
|
+
await waitForStopKey();
|
|
455
|
+
|
|
456
|
+
// Stop algo
|
|
457
|
+
addLog('warning', 'Stopping algo...');
|
|
458
|
+
displayUI();
|
|
459
|
+
|
|
460
|
+
if (hqxConnected) {
|
|
461
|
+
hqxServer.stopAlgo();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
hqxServer.disconnect();
|
|
465
|
+
algoRunning = false;
|
|
466
|
+
|
|
467
|
+
console.log();
|
|
468
|
+
console.log(chalk.green(' [OK] Algo stopped successfully'));
|
|
469
|
+
console.log();
|
|
470
|
+
|
|
471
|
+
// Final stats
|
|
472
|
+
console.log(chalk.gray(getSeparator()));
|
|
473
|
+
console.log(chalk.white.bold(' Session Summary'));
|
|
474
|
+
console.log(chalk.gray(getSeparator()));
|
|
475
|
+
console.log(chalk.white(` Total Trades: ${chalk.cyan(stats.trades)}`));
|
|
476
|
+
console.log(chalk.white(` Wins: ${chalk.green(stats.wins)}`));
|
|
477
|
+
console.log(chalk.white(` Losses: ${chalk.red(stats.losses)}`));
|
|
478
|
+
console.log(chalk.white(` Win Rate: ${chalk.yellow(stats.winRate + '%')}`));
|
|
479
|
+
console.log(chalk.white(` Total P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
|
|
480
|
+
console.log(chalk.gray(getSeparator()));
|
|
481
|
+
console.log();
|
|
482
|
+
|
|
483
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Execute signal via PropFirm API
|
|
488
|
+
*/
|
|
489
|
+
const executeSignal = async (service, account, contract, numContracts, signal) => {
|
|
490
|
+
try {
|
|
491
|
+
const orderData = {
|
|
492
|
+
accountId: account.accountId,
|
|
493
|
+
contractId: contract.id || contract.contractId,
|
|
494
|
+
type: 2, // Market order
|
|
495
|
+
side: signal.side === 'long' ? 0 : 1, // 0=Buy, 1=Sell
|
|
496
|
+
size: numContracts
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// Place order via ProjectX Gateway API
|
|
500
|
+
const result = await service.placeOrder(orderData);
|
|
501
|
+
|
|
502
|
+
if (result.success) {
|
|
503
|
+
console.log(chalk.green(` [OK] Order executed: ${signal.side.toUpperCase()} ${numContracts} contracts`));
|
|
504
|
+
} else {
|
|
505
|
+
console.log(chalk.red(` [X] Order failed: ${result.error || 'Unknown error'}`));
|
|
506
|
+
}
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.log(chalk.red(` [X] Order error: ${error.message}`));
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Wait for X key to stop
|
|
514
|
+
*/
|
|
515
|
+
const waitForStopKey = () => {
|
|
516
|
+
return new Promise((resolve) => {
|
|
517
|
+
// Enable raw mode to capture keypresses
|
|
518
|
+
if (process.stdin.isTTY) {
|
|
519
|
+
readline.emitKeypressEvents(process.stdin);
|
|
520
|
+
process.stdin.setRawMode(true);
|
|
521
|
+
|
|
522
|
+
const onKeypress = (str, key) => {
|
|
523
|
+
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
524
|
+
process.stdin.setRawMode(false);
|
|
525
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
526
|
+
resolve();
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
process.stdin.on('keypress', onKeypress);
|
|
531
|
+
} else {
|
|
532
|
+
// Fallback: wait 30 seconds in non-TTY mode
|
|
533
|
+
setTimeout(resolve, 30000);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
module.exports = { algoTradingMenu };
|
package/src/pages/index.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pages
|
|
2
|
+
* @fileoverview Pages module exports
|
|
3
|
+
* @module pages
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
const { showStats } = require('./stats');
|
|
7
|
+
const { showAccounts } = require('./accounts');
|
|
8
|
+
const { showPositions } = require('./positions');
|
|
9
|
+
const { showOrders } = require('./orders');
|
|
10
|
+
const { showUserInfo } = require('./user');
|
|
11
|
+
const { algoTradingMenu } = require('./algo');
|
|
6
12
|
|
|
7
13
|
module.exports = {
|
|
8
|
-
showStats
|
|
14
|
+
showStats,
|
|
15
|
+
showAccounts,
|
|
16
|
+
showPositions,
|
|
17
|
+
showOrders,
|
|
18
|
+
showUserInfo,
|
|
19
|
+
algoTradingMenu
|
|
9
20
|
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Orders page
|
|
3
|
+
* @module pages/orders
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const inquirer = require('inquirer');
|
|
9
|
+
|
|
10
|
+
const { connections } = require('../services');
|
|
11
|
+
const { ORDER_STATUS, ORDER_TYPE, ORDER_SIDE } = require('../config');
|
|
12
|
+
const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator, visibleLength, padText } = require('../ui');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Shows all orders from all connections
|
|
16
|
+
* @param {Object} service - Current service
|
|
17
|
+
*/
|
|
18
|
+
const showOrders = async (service) => {
|
|
19
|
+
const spinner = ora('Fetching orders...').start();
|
|
20
|
+
const boxWidth = getLogoWidth();
|
|
21
|
+
|
|
22
|
+
// Get all accounts first
|
|
23
|
+
let allAccounts = [];
|
|
24
|
+
|
|
25
|
+
if (connections.count() > 0) {
|
|
26
|
+
for (const conn of connections.getAll()) {
|
|
27
|
+
try {
|
|
28
|
+
const result = await conn.service.getTradingAccounts();
|
|
29
|
+
if (result.success && result.accounts) {
|
|
30
|
+
result.accounts.forEach(account => {
|
|
31
|
+
allAccounts.push({
|
|
32
|
+
...account,
|
|
33
|
+
propfirm: conn.propfirm || conn.type,
|
|
34
|
+
service: conn.service
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
} catch (e) { /* ignore */ }
|
|
39
|
+
}
|
|
40
|
+
} else if (service) {
|
|
41
|
+
const result = await service.getTradingAccounts();
|
|
42
|
+
if (result.success && result.accounts) {
|
|
43
|
+
allAccounts = result.accounts.map(a => ({ ...a, service, propfirm: service.propfirm.name }));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Get orders for each account
|
|
48
|
+
let allOrders = [];
|
|
49
|
+
|
|
50
|
+
for (const account of allAccounts) {
|
|
51
|
+
try {
|
|
52
|
+
const result = await account.service.getOrders(account.accountId);
|
|
53
|
+
if (result.success && result.orders && result.orders.length > 0) {
|
|
54
|
+
result.orders.forEach(order => {
|
|
55
|
+
allOrders.push({
|
|
56
|
+
...order,
|
|
57
|
+
accountName: account.accountName || account.name,
|
|
58
|
+
propfirm: account.propfirm
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} catch (e) { /* ignore */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
spinner.succeed(`Found ${allOrders.length} order(s)`);
|
|
66
|
+
console.log();
|
|
67
|
+
|
|
68
|
+
drawBoxHeader('ORDERS', boxWidth);
|
|
69
|
+
|
|
70
|
+
if (allOrders.length === 0) {
|
|
71
|
+
drawBoxRow(chalk.gray(' No orders found'), boxWidth);
|
|
72
|
+
} else {
|
|
73
|
+
// Header row
|
|
74
|
+
const header = ' ' +
|
|
75
|
+
'Symbol'.padEnd(12) +
|
|
76
|
+
'Side'.padEnd(6) +
|
|
77
|
+
'Type'.padEnd(8) +
|
|
78
|
+
'Qty'.padEnd(6) +
|
|
79
|
+
'Price'.padEnd(10) +
|
|
80
|
+
'Status'.padEnd(12) +
|
|
81
|
+
'Account';
|
|
82
|
+
drawBoxRow(chalk.white.bold(header), boxWidth);
|
|
83
|
+
drawBoxSeparator(boxWidth);
|
|
84
|
+
|
|
85
|
+
// Order rows
|
|
86
|
+
for (const order of allOrders) {
|
|
87
|
+
const symbol = (order.contractId || order.symbol || 'Unknown').substring(0, 11);
|
|
88
|
+
const sideInfo = ORDER_SIDE[order.side] || { text: '?', color: 'white' };
|
|
89
|
+
const type = ORDER_TYPE[order.type] || 'Unknown';
|
|
90
|
+
const qty = order.size || order.quantity || 0;
|
|
91
|
+
const price = order.limitPrice || order.price || 0;
|
|
92
|
+
const statusInfo = ORDER_STATUS[order.status] || { text: 'Unknown', color: 'gray', icon: '[?]' };
|
|
93
|
+
const account = (order.accountName || 'Unknown').substring(0, 12);
|
|
94
|
+
|
|
95
|
+
const row = ' ' +
|
|
96
|
+
chalk.white(symbol.padEnd(12)) +
|
|
97
|
+
chalk[sideInfo.color](sideInfo.text.substring(0, 4).padEnd(6)) +
|
|
98
|
+
chalk.white(type.substring(0, 7).padEnd(8)) +
|
|
99
|
+
chalk.white(qty.toString().padEnd(6)) +
|
|
100
|
+
chalk.white((price > 0 ? price.toFixed(2) : 'MKT').padEnd(10)) +
|
|
101
|
+
chalk[statusInfo.color]((statusInfo.icon + ' ' + statusInfo.text).substring(0, 11).padEnd(12)) +
|
|
102
|
+
chalk.gray(account);
|
|
103
|
+
|
|
104
|
+
drawBoxRow(row, boxWidth);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
drawBoxFooter(boxWidth);
|
|
109
|
+
console.log();
|
|
110
|
+
|
|
111
|
+
await inquirer.prompt([{ type: 'input', name: 'c', message: 'Press Enter to continue...' }]);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
module.exports = { showOrders };
|