hedgequantx 1.2.57 → 1.2.59
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.js +185 -20
package/package.json
CHANGED
package/src/pages/algo.js
CHANGED
|
@@ -215,15 +215,57 @@ const selectSymbolMenu = async (service, account) => {
|
|
|
215
215
|
filter: (input) => parseInt(input)
|
|
216
216
|
}
|
|
217
217
|
]);
|
|
218
|
+
|
|
219
|
+
// Risk Management
|
|
220
|
+
console.log();
|
|
221
|
+
console.log(chalk.cyan.bold(' Risk Management'));
|
|
222
|
+
console.log(chalk.gray(' Set your daily target and maximum risk to auto-stop the algo.'));
|
|
223
|
+
console.log();
|
|
224
|
+
|
|
225
|
+
const { dailyTarget } = await inquirer.prompt([
|
|
226
|
+
{
|
|
227
|
+
type: 'input',
|
|
228
|
+
name: 'dailyTarget',
|
|
229
|
+
message: chalk.white.bold('Daily Target ($):'),
|
|
230
|
+
default: '500',
|
|
231
|
+
validate: (input) => {
|
|
232
|
+
const num = parseFloat(input);
|
|
233
|
+
if (isNaN(num) || num <= 0) {
|
|
234
|
+
return 'Please enter a valid amount greater than 0';
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
},
|
|
238
|
+
filter: (input) => parseFloat(input)
|
|
239
|
+
}
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
const { maxRisk } = await inquirer.prompt([
|
|
243
|
+
{
|
|
244
|
+
type: 'input',
|
|
245
|
+
name: 'maxRisk',
|
|
246
|
+
message: chalk.white.bold('Max Risk ($):'),
|
|
247
|
+
default: '200',
|
|
248
|
+
validate: (input) => {
|
|
249
|
+
const num = parseFloat(input);
|
|
250
|
+
if (isNaN(num) || num <= 0) {
|
|
251
|
+
return 'Please enter a valid amount greater than 0';
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
},
|
|
255
|
+
filter: (input) => parseFloat(input)
|
|
256
|
+
}
|
|
257
|
+
]);
|
|
218
258
|
|
|
219
259
|
// Confirmation
|
|
220
260
|
console.log();
|
|
221
261
|
console.log(chalk.gray(getSeparator()));
|
|
222
262
|
console.log(chalk.white.bold(' Algo Configuration:'));
|
|
223
263
|
console.log(chalk.gray(getSeparator()));
|
|
224
|
-
console.log(chalk.white(` Account:
|
|
225
|
-
console.log(chalk.white(` Symbol:
|
|
226
|
-
console.log(chalk.white(` Contracts:
|
|
264
|
+
console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
|
|
265
|
+
console.log(chalk.white(` Symbol: ${chalk.cyan(contract.name || selectedSymbol.value)}`));
|
|
266
|
+
console.log(chalk.white(` Contracts: ${chalk.cyan(contracts)}`));
|
|
267
|
+
console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
|
|
268
|
+
console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
|
|
227
269
|
console.log(chalk.gray(getSeparator()));
|
|
228
270
|
console.log();
|
|
229
271
|
|
|
@@ -244,13 +286,13 @@ const selectSymbolMenu = async (service, account) => {
|
|
|
244
286
|
return;
|
|
245
287
|
}
|
|
246
288
|
|
|
247
|
-
await launchAlgo(service, account, contract, contracts);
|
|
289
|
+
await launchAlgo(service, account, contract, contracts, dailyTarget, maxRisk);
|
|
248
290
|
};
|
|
249
291
|
|
|
250
292
|
/**
|
|
251
293
|
* Launch Algo with HQX Server Connection
|
|
252
294
|
*/
|
|
253
|
-
const launchAlgo = async (service, account, contract, numContracts) => {
|
|
295
|
+
const launchAlgo = async (service, account, contract, numContracts, dailyTarget, maxRisk) => {
|
|
254
296
|
const accountName = account.accountName || account.name || 'Account #' + account.accountId;
|
|
255
297
|
const symbolName = contract.name || contract.symbol || contract.id;
|
|
256
298
|
const symbol = contract.symbol || contract.id;
|
|
@@ -263,10 +305,11 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
263
305
|
const hqxServer = new HQXServerService();
|
|
264
306
|
let hqxConnected = false;
|
|
265
307
|
let algoRunning = false;
|
|
308
|
+
let stopReason = null;
|
|
266
309
|
|
|
267
310
|
// Activity logs
|
|
268
311
|
const logs = [];
|
|
269
|
-
const MAX_LOGS =
|
|
312
|
+
const MAX_LOGS = 10;
|
|
270
313
|
|
|
271
314
|
// Stats
|
|
272
315
|
let stats = {
|
|
@@ -288,8 +331,30 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
288
331
|
console.clear();
|
|
289
332
|
};
|
|
290
333
|
|
|
334
|
+
// Check market hours
|
|
335
|
+
const checkMarketStatus = () => {
|
|
336
|
+
const now = new Date();
|
|
337
|
+
const utcDay = now.getUTCDay();
|
|
338
|
+
const utcHour = now.getUTCHours();
|
|
339
|
+
const isDST = (() => {
|
|
340
|
+
const jan = new Date(now.getFullYear(), 0, 1);
|
|
341
|
+
const jul = new Date(now.getFullYear(), 6, 1);
|
|
342
|
+
return now.getTimezoneOffset() < Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
|
|
343
|
+
})();
|
|
344
|
+
const ctOffset = isDST ? 5 : 6;
|
|
345
|
+
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
346
|
+
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
347
|
+
|
|
348
|
+
if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
349
|
+
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
350
|
+
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
351
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
|
|
352
|
+
return { isOpen: true, message: 'Market OPEN' };
|
|
353
|
+
};
|
|
354
|
+
|
|
291
355
|
const displayUI = () => {
|
|
292
356
|
clearScreen();
|
|
357
|
+
const marketStatus = checkMarketStatus();
|
|
293
358
|
console.log();
|
|
294
359
|
console.log(chalk.gray(getSeparator()));
|
|
295
360
|
console.log(chalk.cyan.bold(' HQX Ultra-Scalping Algo'));
|
|
@@ -298,8 +363,18 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
298
363
|
console.log(chalk.white(` Account: ${chalk.cyan(accountName)}`));
|
|
299
364
|
console.log(chalk.white(` Symbol: ${chalk.cyan(symbolName)}`));
|
|
300
365
|
console.log(chalk.white(` Contracts: ${chalk.cyan(numContracts)}`));
|
|
301
|
-
console.log(chalk.white(`
|
|
366
|
+
console.log(chalk.white(` Server: ${hqxConnected ? chalk.green('CONNECTED') : chalk.red('DISCONNECTED')}`));
|
|
367
|
+
console.log(chalk.white(` Market: ${marketStatus.isOpen ? chalk.green(marketStatus.message) : chalk.red(marketStatus.message)}`));
|
|
302
368
|
console.log(chalk.gray(getSeparator()));
|
|
369
|
+
|
|
370
|
+
// Risk Management
|
|
371
|
+
console.log();
|
|
372
|
+
const targetProgress = Math.min(100, Math.max(0, (stats.pnl / dailyTarget) * 100));
|
|
373
|
+
const riskProgress = Math.min(100, (Math.abs(Math.min(0, stats.pnl)) / maxRisk) * 100);
|
|
374
|
+
console.log(chalk.white(' Target: ') + chalk.green('$' + dailyTarget.toFixed(2)) +
|
|
375
|
+
chalk.gray(' | Progress: ') + (targetProgress >= 100 ? chalk.green.bold(targetProgress.toFixed(1) + '%') : chalk.yellow(targetProgress.toFixed(1) + '%')));
|
|
376
|
+
console.log(chalk.white(' Risk: ') + chalk.red('$' + maxRisk.toFixed(2)) +
|
|
377
|
+
chalk.gray(' | Used: ') + (riskProgress >= 100 ? chalk.red.bold(riskProgress.toFixed(1) + '%') : chalk.cyan(riskProgress.toFixed(1) + '%')));
|
|
303
378
|
|
|
304
379
|
// Stats bar
|
|
305
380
|
console.log();
|
|
@@ -308,7 +383,7 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
308
383
|
chalk.gray(' | Wins: ') + chalk.green(stats.wins) +
|
|
309
384
|
chalk.gray(' | Losses: ') + chalk.red(stats.losses) +
|
|
310
385
|
chalk.gray(' | Win Rate: ') + chalk.yellow(stats.winRate + '%') +
|
|
311
|
-
chalk.gray(' | P&L: ') + (stats.pnl >= 0 ? chalk.green('
|
|
386
|
+
chalk.gray(' | P&L: ') + (stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2)))
|
|
312
387
|
);
|
|
313
388
|
console.log();
|
|
314
389
|
|
|
@@ -409,6 +484,27 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
409
484
|
addLog('trade', `Closed -$${Math.abs(data.pnl).toFixed(2)} (${data.reason || 'stop_loss'})`);
|
|
410
485
|
}
|
|
411
486
|
stats.winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) : '0.0';
|
|
487
|
+
|
|
488
|
+
// Check daily target
|
|
489
|
+
if (stats.pnl >= dailyTarget) {
|
|
490
|
+
stopReason = 'target';
|
|
491
|
+
addLog('success', `Daily target reached! +$${stats.pnl.toFixed(2)}`);
|
|
492
|
+
algoRunning = false;
|
|
493
|
+
if (hqxConnected) {
|
|
494
|
+
hqxServer.stopAlgo();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Check max risk
|
|
499
|
+
if (stats.pnl <= -maxRisk) {
|
|
500
|
+
stopReason = 'risk';
|
|
501
|
+
addLog('error', `Max risk reached! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
502
|
+
algoRunning = false;
|
|
503
|
+
if (hqxConnected) {
|
|
504
|
+
hqxServer.stopAlgo();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
412
508
|
displayUI();
|
|
413
509
|
});
|
|
414
510
|
|
|
@@ -431,13 +527,14 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
431
527
|
// Start algo
|
|
432
528
|
if (hqxConnected) {
|
|
433
529
|
addLog('info', 'Starting HQX Ultra-Scalping...');
|
|
530
|
+
addLog('info', `Target: $${dailyTarget.toFixed(2)} | Risk: $${maxRisk.toFixed(2)}`);
|
|
434
531
|
hqxServer.startAlgo({
|
|
435
532
|
accountId: account.accountId,
|
|
436
533
|
contractId: contract.id || contract.contractId,
|
|
437
534
|
symbol: symbol,
|
|
438
535
|
contracts: numContracts,
|
|
439
|
-
dailyTarget:
|
|
440
|
-
maxRisk:
|
|
536
|
+
dailyTarget: dailyTarget,
|
|
537
|
+
maxRisk: maxRisk,
|
|
441
538
|
propfirm: account.propfirm || 'projectx',
|
|
442
539
|
propfirmToken: service.getToken ? service.getToken() : null
|
|
443
540
|
});
|
|
@@ -450,14 +547,44 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
450
547
|
|
|
451
548
|
displayUI();
|
|
452
549
|
|
|
453
|
-
// Wait for X key
|
|
454
|
-
await
|
|
550
|
+
// Wait for X key OR auto-stop (target/risk reached)
|
|
551
|
+
await new Promise((resolve) => {
|
|
552
|
+
// Check for auto-stop every 500ms
|
|
553
|
+
const checkInterval = setInterval(() => {
|
|
554
|
+
if (!algoRunning || stopReason) {
|
|
555
|
+
clearInterval(checkInterval);
|
|
556
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
557
|
+
process.stdin.setRawMode(false);
|
|
558
|
+
}
|
|
559
|
+
resolve();
|
|
560
|
+
}
|
|
561
|
+
}, 500);
|
|
562
|
+
|
|
563
|
+
// Also listen for X key
|
|
564
|
+
if (process.stdin.isTTY) {
|
|
565
|
+
readline.emitKeypressEvents(process.stdin);
|
|
566
|
+
process.stdin.setRawMode(true);
|
|
567
|
+
|
|
568
|
+
const onKeypress = (str, key) => {
|
|
569
|
+
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
570
|
+
clearInterval(checkInterval);
|
|
571
|
+
process.stdin.setRawMode(false);
|
|
572
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
573
|
+
resolve();
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
process.stdin.on('keypress', onKeypress);
|
|
578
|
+
}
|
|
579
|
+
});
|
|
455
580
|
|
|
456
581
|
// Stop algo
|
|
457
|
-
|
|
458
|
-
|
|
582
|
+
if (!stopReason) {
|
|
583
|
+
addLog('warning', 'Stopping algo...');
|
|
584
|
+
displayUI();
|
|
585
|
+
}
|
|
459
586
|
|
|
460
|
-
if (hqxConnected) {
|
|
587
|
+
if (hqxConnected && algoRunning) {
|
|
461
588
|
hqxServer.stopAlgo();
|
|
462
589
|
}
|
|
463
590
|
|
|
@@ -465,18 +592,27 @@ const launchAlgo = async (service, account, contract, numContracts) => {
|
|
|
465
592
|
algoRunning = false;
|
|
466
593
|
|
|
467
594
|
console.log();
|
|
468
|
-
|
|
595
|
+
if (stopReason === 'target') {
|
|
596
|
+
console.log(chalk.green.bold(' [OK] Daily target reached! Algo stopped.'));
|
|
597
|
+
} else if (stopReason === 'risk') {
|
|
598
|
+
console.log(chalk.red.bold(' [X] Max risk reached! Algo stopped.'));
|
|
599
|
+
} else {
|
|
600
|
+
console.log(chalk.yellow(' [OK] Algo stopped by user'));
|
|
601
|
+
}
|
|
469
602
|
console.log();
|
|
470
603
|
|
|
471
604
|
// Final stats
|
|
472
605
|
console.log(chalk.gray(getSeparator()));
|
|
473
606
|
console.log(chalk.white.bold(' Session Summary'));
|
|
474
607
|
console.log(chalk.gray(getSeparator()));
|
|
608
|
+
console.log(chalk.white(` Daily Target: ${chalk.green('$' + dailyTarget.toFixed(2))}`));
|
|
609
|
+
console.log(chalk.white(` Max Risk: ${chalk.red('$' + maxRisk.toFixed(2))}`));
|
|
610
|
+
console.log(chalk.white(` Final P&L: ${stats.pnl >= 0 ? chalk.green('+$' + stats.pnl.toFixed(2)) : chalk.red('-$' + Math.abs(stats.pnl).toFixed(2))}`));
|
|
611
|
+
console.log(chalk.gray(getSeparator()));
|
|
475
612
|
console.log(chalk.white(` Total Trades: ${chalk.cyan(stats.trades)}`));
|
|
476
613
|
console.log(chalk.white(` Wins: ${chalk.green(stats.wins)}`));
|
|
477
614
|
console.log(chalk.white(` Losses: ${chalk.red(stats.losses)}`));
|
|
478
615
|
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
616
|
console.log(chalk.gray(getSeparator()));
|
|
481
617
|
console.log();
|
|
482
618
|
|
|
@@ -1032,11 +1168,40 @@ const launchCopyTrading = async (config) => {
|
|
|
1032
1168
|
}
|
|
1033
1169
|
}, 2000); // Check every 2 seconds
|
|
1034
1170
|
|
|
1035
|
-
// Wait for stop
|
|
1036
|
-
await
|
|
1171
|
+
// Wait for X key OR auto-stop (target/risk reached)
|
|
1172
|
+
await new Promise((resolve) => {
|
|
1173
|
+
// Check for auto-stop every 500ms
|
|
1174
|
+
const checkInterval = setInterval(() => {
|
|
1175
|
+
if (!isRunning || stopReason) {
|
|
1176
|
+
clearInterval(checkInterval);
|
|
1177
|
+
clearInterval(monitorInterval);
|
|
1178
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
1179
|
+
process.stdin.setRawMode(false);
|
|
1180
|
+
}
|
|
1181
|
+
resolve();
|
|
1182
|
+
}
|
|
1183
|
+
}, 500);
|
|
1184
|
+
|
|
1185
|
+
// Also listen for X key
|
|
1186
|
+
if (process.stdin.isTTY) {
|
|
1187
|
+
readline.emitKeypressEvents(process.stdin);
|
|
1188
|
+
process.stdin.setRawMode(true);
|
|
1189
|
+
|
|
1190
|
+
const onKeypress = (str, key) => {
|
|
1191
|
+
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
1192
|
+
clearInterval(checkInterval);
|
|
1193
|
+
clearInterval(monitorInterval);
|
|
1194
|
+
process.stdin.setRawMode(false);
|
|
1195
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
1196
|
+
resolve();
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
process.stdin.on('keypress', onKeypress);
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1037
1203
|
|
|
1038
1204
|
// Cleanup
|
|
1039
|
-
clearInterval(monitorInterval);
|
|
1040
1205
|
isRunning = false;
|
|
1041
1206
|
|
|
1042
1207
|
console.log();
|