hedgequantx 2.6.163 → 2.7.0
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 +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +6 -3
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
package/src/pages/algo/index.js
CHANGED
|
@@ -1,122 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Algo Trading -
|
|
2
|
+
* Algo Trading - Main Menu
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
|
-
const {
|
|
7
|
-
const { prompts } = require('../../utils');
|
|
8
|
-
|
|
6
|
+
const { getSeparator } = require('../../ui');
|
|
7
|
+
const { logger, prompts } = require('../../utils');
|
|
8
|
+
|
|
9
|
+
const log = logger.scope('AlgoMenu');
|
|
9
10
|
|
|
10
11
|
const { oneAccountMenu } = require('./one-account');
|
|
11
12
|
const { copyTradingMenu } = require('./copy-trading');
|
|
12
|
-
const { customStrategyMenu } = require('./custom-strategy');
|
|
13
|
-
const aiService = require('../../services/ai');
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
|
-
* Algo Trading Menu
|
|
15
|
+
* Algo Trading Menu
|
|
17
16
|
*/
|
|
18
17
|
const algoTradingMenu = async (service) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
console.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
18
|
+
log.info('Algo Trading menu opened');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(chalk.gray(getSeparator()));
|
|
23
|
+
console.log(chalk.yellow.bold(' Algo-Trading'));
|
|
24
|
+
console.log(chalk.gray(getSeparator()));
|
|
25
|
+
console.log();
|
|
26
|
+
|
|
27
|
+
const action = await prompts.selectOption(chalk.yellow('Select Mode:'), [
|
|
28
|
+
{ value: 'one_account', label: 'One Account' },
|
|
29
|
+
{ value: 'copy_trading', label: 'Copy Trading' },
|
|
30
|
+
{ value: 'back', label: '< Back' }
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
log.debug('Algo mode selected', { action });
|
|
34
|
+
|
|
35
|
+
if (!action || action === 'back') {
|
|
36
|
+
return 'back';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (action) {
|
|
40
|
+
case 'one_account':
|
|
41
|
+
log.info('Starting One Account mode');
|
|
42
|
+
await oneAccountMenu(service);
|
|
43
|
+
break;
|
|
44
|
+
case 'copy_trading':
|
|
45
|
+
log.info('Starting Copy Trading mode');
|
|
46
|
+
await copyTradingMenu();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
50
49
|
|
|
50
|
+
return action;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
log.error('Algo menu error:', err.message);
|
|
53
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
51
54
|
await prompts.waitForEnter();
|
|
52
55
|
return 'back';
|
|
53
56
|
}
|
|
54
|
-
|
|
55
|
-
const boxWidth = getLogoWidth();
|
|
56
|
-
const W = boxWidth - 2;
|
|
57
|
-
|
|
58
|
-
const makeLine = (content) => {
|
|
59
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
60
|
-
const padding = W - plainLen;
|
|
61
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Check if AI agent is connected
|
|
65
|
-
const agents = aiService.getAgents();
|
|
66
|
-
const hasAgent = agents && agents.length > 0;
|
|
67
|
-
|
|
68
|
-
console.clear();
|
|
69
|
-
displayBanner();
|
|
70
|
-
drawBoxHeaderContinue('ALGO TRADING', boxWidth);
|
|
71
|
-
|
|
72
|
-
// Menu options - show CUSTOM STRATEGY only if agent connected
|
|
73
|
-
let menuText, plainLen;
|
|
74
|
-
if (hasAgent) {
|
|
75
|
-
menuText = chalk.cyan('[1]') + chalk.yellow(' ONE ACCOUNT ') + chalk.cyan('[2]') + chalk.yellow(' COPY TRADING ') + chalk.cyan('[3]') + chalk.green(' CUSTOM STRATEGY');
|
|
76
|
-
plainLen = '[1] ONE ACCOUNT [2] COPY TRADING [3] CUSTOM STRATEGY'.length;
|
|
77
|
-
} else {
|
|
78
|
-
menuText = chalk.cyan('[1]') + chalk.yellow(' ONE ACCOUNT ') + chalk.cyan('[2]') + chalk.yellow(' COPY TRADING');
|
|
79
|
-
plainLen = '[1] ONE ACCOUNT [2] COPY TRADING'.length;
|
|
80
|
-
}
|
|
81
|
-
const leftPad = Math.floor((W - plainLen) / 2);
|
|
82
|
-
const rightPad = W - plainLen - leftPad;
|
|
83
|
-
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + menuText + ' '.repeat(rightPad) + chalk.cyan('║'));
|
|
84
|
-
|
|
85
|
-
drawBoxFooter(boxWidth);
|
|
86
|
-
|
|
87
|
-
const choice = await prompts.textInput(chalk.cyan('SELECT:'));
|
|
88
|
-
|
|
89
|
-
if (choice === '1') {
|
|
90
|
-
try {
|
|
91
|
-
await oneAccountMenu(service);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
console.log(chalk.red(`\n ERROR: ${err.message}`));
|
|
94
|
-
console.log(chalk.gray(` ${err.stack}`));
|
|
95
|
-
await prompts.waitForEnter();
|
|
96
|
-
}
|
|
97
|
-
return algoTradingMenu(service);
|
|
98
|
-
} else if (choice === '2') {
|
|
99
|
-
try {
|
|
100
|
-
await copyTradingMenu();
|
|
101
|
-
} catch (err) {
|
|
102
|
-
console.log(chalk.red(`\n ERROR: ${err.message}`));
|
|
103
|
-
console.log(chalk.gray(` ${err.stack}`));
|
|
104
|
-
await prompts.waitForEnter();
|
|
105
|
-
}
|
|
106
|
-
return algoTradingMenu(service);
|
|
107
|
-
} else if (choice === '3' && hasAgent) {
|
|
108
|
-
try {
|
|
109
|
-
await customStrategyMenu(service);
|
|
110
|
-
} catch (err) {
|
|
111
|
-
console.log(chalk.red(`\n ERROR: ${err.message}`));
|
|
112
|
-
console.log(chalk.gray(` ${err.stack}`));
|
|
113
|
-
await prompts.waitForEnter();
|
|
114
|
-
}
|
|
115
|
-
return algoTradingMenu(service);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Empty or other = back
|
|
119
|
-
return 'back';
|
|
120
57
|
};
|
|
121
58
|
|
|
122
59
|
module.exports = { algoTradingMenu };
|
|
@@ -1,30 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One Account Mode - HQX Ultra Scalping
|
|
3
|
-
*
|
|
4
|
-
* FAST PATH: Rithmic direct execution (~10-50ms latency)
|
|
5
|
-
* SLOW PATH: ProjectX/Tradovate HTTP REST (~50-150ms latency)
|
|
6
3
|
*/
|
|
7
4
|
|
|
8
5
|
const chalk = require('chalk');
|
|
9
6
|
const ora = require('ora');
|
|
7
|
+
const readline = require('readline');
|
|
10
8
|
|
|
11
9
|
const { connections } = require('../../services');
|
|
10
|
+
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
12
11
|
const { prompts } = require('../../utils');
|
|
12
|
+
const { checkMarketHours } = require('../../services/rithmic/market');
|
|
13
|
+
|
|
14
|
+
// Strategy & Market Data
|
|
15
|
+
const { M1 } = require('../../lib/m/s1');
|
|
16
|
+
const { MarketDataFeed } = require('../../lib/data');
|
|
17
|
+
|
|
13
18
|
|
|
14
|
-
// Shared utilities
|
|
15
|
-
const { isRithmicFastPath, MAX_MULTI_SYMBOLS } = require('./algo-utils');
|
|
16
|
-
const { selectSymbol, configureAlgo } = require('./algo-config');
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* One Account Menu
|
|
20
22
|
*/
|
|
21
23
|
const oneAccountMenu = async (service) => {
|
|
22
|
-
|
|
24
|
+
// Check if market is open (skip early close check - market may still be open)
|
|
25
|
+
const market = checkMarketHours();
|
|
26
|
+
if (!market.isOpen && !market.message.includes('early')) {
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(chalk.red(` ${market.message}`));
|
|
29
|
+
console.log(chalk.gray(' Algo trading is only available when market is open'));
|
|
30
|
+
console.log();
|
|
31
|
+
await prompts.waitForEnter();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const spinner = ora({ text: 'Fetching active accounts...', color: 'yellow' }).start();
|
|
23
36
|
|
|
24
37
|
const allAccounts = await connections.getAllAccounts();
|
|
25
38
|
|
|
26
39
|
if (!allAccounts?.length) {
|
|
27
|
-
spinner.fail('
|
|
40
|
+
spinner.fail('No accounts found');
|
|
28
41
|
await prompts.waitForEnter();
|
|
29
42
|
return;
|
|
30
43
|
}
|
|
@@ -32,16 +45,16 @@ const oneAccountMenu = async (service) => {
|
|
|
32
45
|
const activeAccounts = allAccounts.filter(acc => acc.status === 0);
|
|
33
46
|
|
|
34
47
|
if (!activeAccounts.length) {
|
|
35
|
-
spinner.fail('
|
|
48
|
+
spinner.fail('No active accounts');
|
|
36
49
|
await prompts.waitForEnter();
|
|
37
50
|
return;
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
spinner.succeed(`
|
|
53
|
+
spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
|
|
41
54
|
|
|
42
55
|
// Select account - display RAW API fields
|
|
43
56
|
const options = activeAccounts.map(acc => {
|
|
44
|
-
// Use what API returns:
|
|
57
|
+
// Use what API returns: rithmicAccountId or accountName for Rithmic
|
|
45
58
|
const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
|
|
46
59
|
const balance = acc.balance !== null && acc.balance !== undefined
|
|
47
60
|
? ` - $${acc.balance.toLocaleString()}`
|
|
@@ -51,40 +64,380 @@ const oneAccountMenu = async (service) => {
|
|
|
51
64
|
value: acc
|
|
52
65
|
};
|
|
53
66
|
});
|
|
54
|
-
options.push({ label: '<
|
|
67
|
+
options.push({ label: '< Back', value: 'back' });
|
|
55
68
|
|
|
56
|
-
const selectedAccount = await prompts.selectOption('
|
|
69
|
+
const selectedAccount = await prompts.selectOption('Select Account:', options);
|
|
57
70
|
if (!selectedAccount || selectedAccount === 'back') return;
|
|
58
71
|
|
|
59
72
|
// Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
|
|
60
73
|
const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
|
|
61
74
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
75
|
+
// Select symbol
|
|
76
|
+
const contract = await selectSymbol(accountService, selectedAccount);
|
|
77
|
+
if (!contract) return;
|
|
64
78
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
// Configure algo
|
|
80
|
+
const config = await configureAlgo(selectedAccount, contract);
|
|
81
|
+
if (!config) return;
|
|
82
|
+
|
|
83
|
+
await launchAlgo(accountService, selectedAccount, contract, config);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Symbol selection - sorted with popular indices first
|
|
88
|
+
*/
|
|
89
|
+
const selectSymbol = async (service, account) => {
|
|
90
|
+
const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
|
|
91
|
+
|
|
92
|
+
const contractsResult = await service.getContracts();
|
|
93
|
+
if (!contractsResult.success || !contractsResult.contracts?.length) {
|
|
94
|
+
spinner.fail('Failed to load contracts');
|
|
95
|
+
return null;
|
|
71
96
|
}
|
|
72
97
|
|
|
73
|
-
|
|
74
|
-
const contracts = await selectSymbol(accountService, selectedAccount, useMultiSymbol);
|
|
75
|
-
if (!contracts) return;
|
|
98
|
+
let contracts = contractsResult.contracts;
|
|
76
99
|
|
|
77
|
-
//
|
|
78
|
-
const
|
|
100
|
+
// Sort: Popular indices first (ES, NQ, MES, MNQ, RTY, YM, etc.)
|
|
101
|
+
const popularPrefixes = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
|
|
79
102
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
103
|
+
contracts.sort((a, b) => {
|
|
104
|
+
const nameA = a.name || '';
|
|
105
|
+
const nameB = b.name || '';
|
|
106
|
+
|
|
107
|
+
// Check if names start with popular prefixes
|
|
108
|
+
const idxA = popularPrefixes.findIndex(p => nameA.startsWith(p));
|
|
109
|
+
const idxB = popularPrefixes.findIndex(p => nameB.startsWith(p));
|
|
110
|
+
|
|
111
|
+
// Both are popular - sort by popularity order
|
|
112
|
+
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
|
|
113
|
+
// Only A is popular - A first
|
|
114
|
+
if (idxA !== -1) return -1;
|
|
115
|
+
// Only B is popular - B first
|
|
116
|
+
if (idxB !== -1) return 1;
|
|
117
|
+
// Neither - alphabetical
|
|
118
|
+
return nameA.localeCompare(nameB);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
spinner.succeed(`Found ${contracts.length} contracts`);
|
|
122
|
+
|
|
123
|
+
// Display sorted contracts from API
|
|
124
|
+
const options = contracts.map(c => ({
|
|
125
|
+
label: `${c.name} - ${c.description}`,
|
|
126
|
+
value: c
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
options.push({ label: chalk.gray('< Back'), value: 'back' });
|
|
83
130
|
|
|
84
|
-
|
|
85
|
-
|
|
131
|
+
const contract = await prompts.selectOption(chalk.yellow('Select Symbol:'), options);
|
|
132
|
+
return contract === 'back' || contract === null ? null : contract;
|
|
86
133
|
};
|
|
87
134
|
|
|
88
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Configure algo
|
|
137
|
+
*/
|
|
138
|
+
const configureAlgo = async (account, contract) => {
|
|
139
|
+
console.log();
|
|
140
|
+
console.log(chalk.cyan(' Configure Algo Parameters'));
|
|
141
|
+
console.log();
|
|
142
|
+
|
|
143
|
+
const contracts = await prompts.numberInput('Number of contracts:', 1, 1, 10);
|
|
144
|
+
if (contracts === null) return null;
|
|
145
|
+
|
|
146
|
+
const dailyTarget = await prompts.numberInput('Daily target ($):', 200, 1, 10000);
|
|
147
|
+
if (dailyTarget === null) return null;
|
|
148
|
+
|
|
149
|
+
const maxRisk = await prompts.numberInput('Max risk ($):', 100, 1, 5000);
|
|
150
|
+
if (maxRisk === null) return null;
|
|
151
|
+
|
|
152
|
+
const showName = await prompts.confirmPrompt('Show account name?', false);
|
|
153
|
+
if (showName === null) return null;
|
|
154
|
+
|
|
155
|
+
const confirm = await prompts.confirmPrompt('Start algo trading?', true);
|
|
156
|
+
if (!confirm) return null;
|
|
157
|
+
|
|
158
|
+
return { contracts, dailyTarget, maxRisk, showName };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Launch algo trading - HQX Ultra Scalping Strategy
|
|
163
|
+
* Real-time market data + Strategy signals + Auto order execution
|
|
164
|
+
*/
|
|
165
|
+
const launchAlgo = async (service, account, contract, config) => {
|
|
166
|
+
const { contracts, dailyTarget, maxRisk, showName } = config;
|
|
167
|
+
|
|
168
|
+
// Use RAW API fields
|
|
169
|
+
const accountName = showName
|
|
170
|
+
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
171
|
+
: 'HQX *****';
|
|
172
|
+
const symbolName = contract.name;
|
|
173
|
+
const contractId = contract.id;
|
|
174
|
+
const connectionType = account.platform || 'Rithmic';
|
|
175
|
+
const tickSize = contract.tickSize || 0.25;
|
|
176
|
+
|
|
177
|
+
const ui = new AlgoUI({ subtitle: 'HQX Ultra Scalping', mode: 'one-account' });
|
|
178
|
+
|
|
179
|
+
const stats = {
|
|
180
|
+
accountName,
|
|
181
|
+
symbol: symbolName,
|
|
182
|
+
qty: contracts,
|
|
183
|
+
target: dailyTarget,
|
|
184
|
+
risk: maxRisk,
|
|
185
|
+
propfirm: account.propfirm || 'Unknown',
|
|
186
|
+
platform: connectionType,
|
|
187
|
+
pnl: 0,
|
|
188
|
+
trades: 0,
|
|
189
|
+
wins: 0,
|
|
190
|
+
losses: 0,
|
|
191
|
+
latency: 0,
|
|
192
|
+
connected: false,
|
|
193
|
+
startTime: Date.now()
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
let running = true;
|
|
197
|
+
let stopReason = null;
|
|
198
|
+
let startingPnL = null;
|
|
199
|
+
let currentPosition = 0; // Current position qty (+ long, - short)
|
|
200
|
+
let pendingOrder = false; // Prevent duplicate orders
|
|
201
|
+
let tickCount = 0;
|
|
202
|
+
|
|
203
|
+
// Initialize Strategy
|
|
204
|
+
const strategy = new M1({ tickSize });
|
|
205
|
+
strategy.initialize(contractId, tickSize);
|
|
206
|
+
|
|
207
|
+
// Initialize Market Data Feed
|
|
208
|
+
const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
|
|
209
|
+
|
|
210
|
+
// Log startup
|
|
211
|
+
ui.addLog('info', `Connection: ${connectionType}`);
|
|
212
|
+
ui.addLog('info', `Account: ${accountName}`);
|
|
213
|
+
ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
|
|
214
|
+
ui.addLog('info', `Target: $${dailyTarget} | Max Risk: $${maxRisk}`);
|
|
215
|
+
ui.addLog('info', 'Connecting to market data...');
|
|
216
|
+
|
|
217
|
+
// Handle strategy signals
|
|
218
|
+
strategy.on('signal', async (signal) => {
|
|
219
|
+
if (!running || pendingOrder || currentPosition !== 0) return;
|
|
220
|
+
|
|
221
|
+
const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
|
|
222
|
+
|
|
223
|
+
ui.addLog('signal', `${direction.toUpperCase()} signal @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
|
|
224
|
+
|
|
225
|
+
// Place order via API
|
|
226
|
+
pendingOrder = true;
|
|
227
|
+
try {
|
|
228
|
+
const orderSide = direction === 'long' ? 0 : 1; // 0=Buy, 1=Sell
|
|
229
|
+
const orderResult = await service.placeOrder({
|
|
230
|
+
accountId: account.accountId,
|
|
231
|
+
contractId: contractId,
|
|
232
|
+
type: 2, // Market order
|
|
233
|
+
side: orderSide,
|
|
234
|
+
size: contracts
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (orderResult.success) {
|
|
238
|
+
currentPosition = direction === 'long' ? contracts : -contracts;
|
|
239
|
+
stats.trades++;
|
|
240
|
+
ui.addLog('trade', `OPENED ${direction.toUpperCase()} ${contracts}x @ market`);
|
|
241
|
+
|
|
242
|
+
// Place bracket orders (SL/TP)
|
|
243
|
+
if (stopLoss && takeProfit) {
|
|
244
|
+
// Stop Loss
|
|
245
|
+
await service.placeOrder({
|
|
246
|
+
accountId: account.accountId,
|
|
247
|
+
contractId: contractId,
|
|
248
|
+
type: 4, // Stop order
|
|
249
|
+
side: direction === 'long' ? 1 : 0, // Opposite side
|
|
250
|
+
size: contracts,
|
|
251
|
+
stopPrice: stopLoss
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Take Profit
|
|
255
|
+
await service.placeOrder({
|
|
256
|
+
accountId: account.accountId,
|
|
257
|
+
contractId: contractId,
|
|
258
|
+
type: 1, // Limit order
|
|
259
|
+
side: direction === 'long' ? 1 : 0,
|
|
260
|
+
size: contracts,
|
|
261
|
+
limitPrice: takeProfit
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
ui.addLog('error', `Order failed: ${orderResult.error}`);
|
|
268
|
+
}
|
|
269
|
+
} catch (e) {
|
|
270
|
+
ui.addLog('error', `Order error: ${e.message}`);
|
|
271
|
+
}
|
|
272
|
+
pendingOrder = false;
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Handle market data ticks
|
|
276
|
+
marketFeed.on('tick', (tick) => {
|
|
277
|
+
tickCount++;
|
|
278
|
+
const latencyStart = Date.now();
|
|
279
|
+
|
|
280
|
+
// Feed tick to strategy
|
|
281
|
+
strategy.processTick({
|
|
282
|
+
contractId: tick.contractId || contractId,
|
|
283
|
+
price: tick.price,
|
|
284
|
+
bid: tick.bid,
|
|
285
|
+
ask: tick.ask,
|
|
286
|
+
volume: tick.volume || 1,
|
|
287
|
+
side: tick.lastTradeSide || 'unknown',
|
|
288
|
+
timestamp: tick.timestamp || Date.now()
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
stats.latency = Date.now() - latencyStart;
|
|
292
|
+
|
|
293
|
+
// Log every 100th tick to show activity
|
|
294
|
+
if (tickCount % 100 === 0) {
|
|
295
|
+
ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
marketFeed.on('connected', () => {
|
|
300
|
+
stats.connected = true;
|
|
301
|
+
ui.addLog('success', 'Market data connected!');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
marketFeed.on('error', (err) => {
|
|
305
|
+
ui.addLog('error', `Market: ${err.message}`);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
marketFeed.on('disconnected', () => {
|
|
309
|
+
stats.connected = false;
|
|
310
|
+
ui.addLog('error', 'Market data disconnected');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Connect to market data
|
|
314
|
+
try {
|
|
315
|
+
const token = service.token || service.getToken?.();
|
|
316
|
+
const propfirmKey = (account.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
|
|
317
|
+
await marketFeed.connect(token, propfirmKey, contractId);
|
|
318
|
+
await marketFeed.subscribe(symbolName, contractId);
|
|
319
|
+
} catch (e) {
|
|
320
|
+
ui.addLog('error', `Failed to connect: ${e.message}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Poll account P&L from API
|
|
324
|
+
const pollPnL = async () => {
|
|
325
|
+
try {
|
|
326
|
+
const accountResult = await service.getTradingAccounts();
|
|
327
|
+
if (accountResult.success && accountResult.accounts) {
|
|
328
|
+
const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
|
|
329
|
+
if (acc && acc.profitAndLoss !== undefined) {
|
|
330
|
+
if (startingPnL === null) startingPnL = acc.profitAndLoss;
|
|
331
|
+
stats.pnl = acc.profitAndLoss - startingPnL;
|
|
332
|
+
|
|
333
|
+
// Record trade result in strategy
|
|
334
|
+
if (stats.pnl !== 0) {
|
|
335
|
+
strategy.recordTradeResult(stats.pnl);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check positions
|
|
341
|
+
const posResult = await service.getPositions(account.accountId);
|
|
342
|
+
if (posResult.success && posResult.positions) {
|
|
343
|
+
const pos = posResult.positions.find(p => {
|
|
344
|
+
const sym = p.contractId || p.symbol || '';
|
|
345
|
+
return sym.includes(contract.name) || sym.includes(contractId);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (pos && pos.quantity !== 0) {
|
|
349
|
+
currentPosition = pos.quantity;
|
|
350
|
+
const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
|
|
351
|
+
const pnl = pos.profitAndLoss || 0;
|
|
352
|
+
|
|
353
|
+
// Check if position closed (win/loss)
|
|
354
|
+
if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
|
|
355
|
+
else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
|
|
356
|
+
} else {
|
|
357
|
+
currentPosition = 0;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Check target/risk
|
|
362
|
+
if (stats.pnl >= dailyTarget) {
|
|
363
|
+
stopReason = 'target';
|
|
364
|
+
running = false;
|
|
365
|
+
ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
|
|
366
|
+
} else if (stats.pnl <= -maxRisk) {
|
|
367
|
+
stopReason = 'risk';
|
|
368
|
+
running = false;
|
|
369
|
+
ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
370
|
+
}
|
|
371
|
+
} catch (e) {
|
|
372
|
+
// Silently handle polling errors
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Start polling and UI refresh
|
|
377
|
+
const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
|
|
378
|
+
const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
|
|
379
|
+
pollPnL(); // Initial poll
|
|
380
|
+
|
|
381
|
+
// Keyboard handler
|
|
382
|
+
const setupKeyHandler = () => {
|
|
383
|
+
if (!process.stdin.isTTY) return;
|
|
384
|
+
readline.emitKeypressEvents(process.stdin);
|
|
385
|
+
process.stdin.setRawMode(true);
|
|
386
|
+
process.stdin.resume();
|
|
387
|
+
|
|
388
|
+
const onKey = (str, key) => {
|
|
389
|
+
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
390
|
+
running = false;
|
|
391
|
+
stopReason = 'manual';
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
process.stdin.on('keypress', onKey);
|
|
395
|
+
return () => {
|
|
396
|
+
process.stdin.removeListener('keypress', onKey);
|
|
397
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
398
|
+
};
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const cleanupKeys = setupKeyHandler();
|
|
402
|
+
|
|
403
|
+
// Wait for stop
|
|
404
|
+
await new Promise(resolve => {
|
|
405
|
+
const check = setInterval(() => {
|
|
406
|
+
if (!running) {
|
|
407
|
+
clearInterval(check);
|
|
408
|
+
resolve();
|
|
409
|
+
}
|
|
410
|
+
}, 100);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Cleanup
|
|
414
|
+
clearInterval(refreshInterval);
|
|
415
|
+
clearInterval(pnlInterval);
|
|
416
|
+
await marketFeed.disconnect();
|
|
417
|
+
if (cleanupKeys) cleanupKeys();
|
|
418
|
+
ui.cleanup();
|
|
419
|
+
|
|
420
|
+
if (process.stdin.isTTY) {
|
|
421
|
+
process.stdin.setRawMode(false);
|
|
422
|
+
}
|
|
423
|
+
process.stdin.resume();
|
|
424
|
+
|
|
425
|
+
// Duration
|
|
426
|
+
const durationMs = Date.now() - stats.startTime;
|
|
427
|
+
const hours = Math.floor(durationMs / 3600000);
|
|
428
|
+
const minutes = Math.floor((durationMs % 3600000) / 60000);
|
|
429
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
430
|
+
stats.duration = hours > 0
|
|
431
|
+
? `${hours}h ${minutes}m ${seconds}s`
|
|
432
|
+
: minutes > 0
|
|
433
|
+
? `${minutes}m ${seconds}s`
|
|
434
|
+
: `${seconds}s`;
|
|
435
|
+
|
|
436
|
+
// Summary
|
|
437
|
+
renderSessionSummary(stats, stopReason);
|
|
438
|
+
|
|
439
|
+
console.log('\n Returning to menu in 3 seconds...');
|
|
440
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
441
|
+
};
|
|
89
442
|
|
|
90
|
-
module.exports = { oneAccountMenu
|
|
443
|
+
module.exports = { oneAccountMenu };
|