hedgequantx 1.7.7 → 1.7.8
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 -2
- package/src/app.js +25 -9
- package/src/menus/dashboard.js +14 -10
- package/src/utils/prompts.js +157 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hedgequantx",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
|
|
5
5
|
"main": "src/app.js",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
"src/"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@clack/prompts": "^0.11.0",
|
|
45
44
|
"asciichart": "^1.5.25",
|
|
46
45
|
"chalk": "^4.1.2",
|
|
47
46
|
"commander": "^11.1.0",
|
package/src/app.js
CHANGED
|
@@ -133,7 +133,7 @@ const banner = async () => {
|
|
|
133
133
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
134
134
|
const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
|
|
135
135
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
136
|
-
|
|
136
|
+
// No closing line - dashboard will continue the box
|
|
137
137
|
};
|
|
138
138
|
|
|
139
139
|
/**
|
|
@@ -142,20 +142,36 @@ const banner = async () => {
|
|
|
142
142
|
const mainMenu = async () => {
|
|
143
143
|
const boxWidth = getLogoWidth();
|
|
144
144
|
const innerWidth = boxWidth - 2;
|
|
145
|
+
const col1Width = Math.floor(innerWidth / 2);
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
const menuRow = (left, right) => {
|
|
148
|
+
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
149
|
+
const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
150
|
+
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
151
|
+
const rightPadded = (right || '') + ' '.repeat(Math.max(0, innerWidth - col1Width - rightPlain.length));
|
|
152
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Continue from banner
|
|
156
|
+
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
147
157
|
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PLATFORM', innerWidth)) + chalk.cyan('║'));
|
|
148
158
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
159
|
+
|
|
160
|
+
menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
|
|
161
|
+
menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Exit'));
|
|
162
|
+
|
|
149
163
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
150
164
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
const input = await prompts.textInput('Select (1/2/3/X)');
|
|
166
|
+
|
|
167
|
+
const actionMap = {
|
|
168
|
+
'1': 'projectx',
|
|
169
|
+
'2': 'rithmic',
|
|
170
|
+
'3': 'tradovate',
|
|
171
|
+
'x': 'exit'
|
|
172
|
+
};
|
|
157
173
|
|
|
158
|
-
return
|
|
174
|
+
return actionMap[(input || '').toLowerCase()] || 'exit';
|
|
159
175
|
};
|
|
160
176
|
|
|
161
177
|
/**
|
package/src/menus/dashboard.js
CHANGED
|
@@ -30,7 +30,8 @@ const dashboardMenu = async (service) => {
|
|
|
30
30
|
return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Continue from banner (no top border)
|
|
34
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
34
35
|
console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
|
|
35
36
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
36
37
|
|
|
@@ -89,16 +90,19 @@ const dashboardMenu = async (service) => {
|
|
|
89
90
|
|
|
90
91
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
// Simple input - no duplicate menu
|
|
94
|
+
const input = await prompts.textInput('Select (1/2/+/A/U/X)');
|
|
95
|
+
|
|
96
|
+
const actionMap = {
|
|
97
|
+
'1': 'accounts',
|
|
98
|
+
'2': 'stats',
|
|
99
|
+
'+': 'add_prop_account',
|
|
100
|
+
'a': 'algotrading',
|
|
101
|
+
'u': 'update',
|
|
102
|
+
'x': 'disconnect'
|
|
103
|
+
};
|
|
100
104
|
|
|
101
|
-
return
|
|
105
|
+
return actionMap[(input || '').toLowerCase()] || null;
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
/**
|
package/src/utils/prompts.js
CHANGED
|
@@ -1,80 +1,189 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Centralized prompts utility
|
|
3
|
-
*
|
|
2
|
+
* Centralized prompts utility - lightweight, no external UI
|
|
3
|
+
* Uses readline for simple input, keeps our custom box design
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { select, text, password, confirm, isCancel } = require('@clack/prompts');
|
|
7
6
|
const readline = require('readline');
|
|
8
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Create readline interface
|
|
10
|
+
*/
|
|
11
|
+
const createRL = () => readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout
|
|
14
|
+
});
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* Wait for Enter key
|
|
11
18
|
*/
|
|
12
19
|
const waitForEnter = (message = 'Press Enter to continue...') => new Promise(resolve => {
|
|
13
|
-
const rl =
|
|
20
|
+
const rl = createRL();
|
|
14
21
|
rl.question(message, () => { rl.close(); resolve(); });
|
|
15
22
|
});
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
|
-
*
|
|
25
|
+
* Simple text input
|
|
19
26
|
*/
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const textInput = (message, defaultVal = '') => new Promise(resolve => {
|
|
28
|
+
const rl = createRL();
|
|
29
|
+
const prompt = defaultVal ? `${message} (${defaultVal}): ` : `${message}: `;
|
|
30
|
+
rl.question(prompt, (answer) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
resolve(answer.trim() || defaultVal);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
25
35
|
|
|
26
36
|
/**
|
|
27
|
-
*
|
|
37
|
+
* Password input (hidden)
|
|
28
38
|
*/
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
if (validate) opts.validate = validate;
|
|
39
|
+
const passwordInput = (message) => new Promise(resolve => {
|
|
40
|
+
const rl = createRL();
|
|
41
|
+
process.stdout.write(`${message}: `);
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
if (process.stdin.isTTY) {
|
|
44
|
+
process.stdin.setRawMode(true);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let password = '';
|
|
48
|
+
const onData = (char) => {
|
|
49
|
+
char = char.toString();
|
|
50
|
+
|
|
51
|
+
if (char === '\n' || char === '\r') {
|
|
52
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
53
|
+
process.stdin.removeListener('data', onData);
|
|
54
|
+
console.log();
|
|
55
|
+
rl.close();
|
|
56
|
+
resolve(password);
|
|
57
|
+
} else if (char === '\u0003') { // Ctrl+C
|
|
58
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
59
|
+
process.stdin.removeListener('data', onData);
|
|
60
|
+
rl.close();
|
|
61
|
+
resolve(null);
|
|
62
|
+
} else if (char === '\u007F' || char === '\b') { // Backspace
|
|
63
|
+
if (password.length > 0) {
|
|
64
|
+
password = password.slice(0, -1);
|
|
65
|
+
process.stdout.write('\b \b');
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
password += char;
|
|
69
|
+
process.stdout.write('*');
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
process.stdin.on('data', onData);
|
|
74
|
+
process.stdin.resume();
|
|
75
|
+
});
|
|
38
76
|
|
|
39
77
|
/**
|
|
40
|
-
*
|
|
78
|
+
* Select from options using arrow keys
|
|
41
79
|
*/
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
const selectOption = (message, options) => new Promise(resolve => {
|
|
81
|
+
if (!process.stdin.isTTY) {
|
|
82
|
+
// Fallback for non-TTY
|
|
83
|
+
const rl = createRL();
|
|
84
|
+
console.log(message);
|
|
85
|
+
options.forEach((opt, i) => console.log(` ${i + 1}. ${opt.label}`));
|
|
86
|
+
rl.question('Enter number: ', (answer) => {
|
|
87
|
+
rl.close();
|
|
88
|
+
const idx = parseInt(answer) - 1;
|
|
89
|
+
resolve(options[idx]?.value || null);
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
45
93
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
94
|
+
let selectedIndex = 0;
|
|
95
|
+
const maxIndex = options.length - 1;
|
|
96
|
+
|
|
97
|
+
const render = () => {
|
|
98
|
+
// Move cursor up and clear lines
|
|
99
|
+
if (selectedIndex > 0 || options.length > 1) {
|
|
100
|
+
process.stdout.write(`\x1B[${options.length}A`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
options.forEach((opt, i) => {
|
|
104
|
+
const prefix = i === selectedIndex ? '› ' : ' ';
|
|
105
|
+
const style = i === selectedIndex ? '\x1B[36m' : '\x1B[90m'; // cyan : gray
|
|
106
|
+
process.stdout.write(`\x1B[2K${style}${prefix}${opt.label}\x1B[0m\n`);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Initial render
|
|
111
|
+
console.log(`\x1B[36m${message}\x1B[0m`);
|
|
112
|
+
options.forEach((opt, i) => {
|
|
113
|
+
const prefix = i === selectedIndex ? '› ' : ' ';
|
|
114
|
+
const style = i === selectedIndex ? '\x1B[36m' : '\x1B[90m';
|
|
115
|
+
console.log(`${style}${prefix}${opt.label}\x1B[0m`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
readline.emitKeypressEvents(process.stdin);
|
|
119
|
+
process.stdin.setRawMode(true);
|
|
120
|
+
process.stdin.resume();
|
|
121
|
+
|
|
122
|
+
const onKeypress = (str, key) => {
|
|
123
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
124
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : maxIndex;
|
|
125
|
+
render();
|
|
126
|
+
} else if (key.name === 'down' || key.name === 'j') {
|
|
127
|
+
selectedIndex = selectedIndex < maxIndex ? selectedIndex + 1 : 0;
|
|
128
|
+
render();
|
|
129
|
+
} else if (key.name === 'return') {
|
|
130
|
+
cleanup();
|
|
131
|
+
resolve(options[selectedIndex].value);
|
|
132
|
+
} else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
|
|
133
|
+
cleanup();
|
|
134
|
+
resolve(null);
|
|
135
|
+
} else if (str >= '1' && str <= '9') {
|
|
136
|
+
const idx = parseInt(str) - 1;
|
|
137
|
+
if (idx <= maxIndex) {
|
|
138
|
+
cleanup();
|
|
139
|
+
resolve(options[idx].value);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const cleanup = () => {
|
|
145
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
146
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
147
|
+
// Clear the menu display
|
|
148
|
+
process.stdout.write(`\x1B[${options.length + 1}A`);
|
|
149
|
+
for (let i = 0; i <= options.length; i++) {
|
|
150
|
+
process.stdout.write('\x1B[2K\n');
|
|
151
|
+
}
|
|
152
|
+
process.stdout.write(`\x1B[${options.length + 1}A`);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
process.stdin.on('keypress', onKeypress);
|
|
156
|
+
});
|
|
50
157
|
|
|
51
158
|
/**
|
|
52
159
|
* Confirm yes/no
|
|
53
160
|
*/
|
|
54
|
-
const confirmPrompt =
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
161
|
+
const confirmPrompt = (message, defaultVal = true) => new Promise(resolve => {
|
|
162
|
+
const rl = createRL();
|
|
163
|
+
const hint = defaultVal ? '(Y/n)' : '(y/N)';
|
|
164
|
+
rl.question(`${message} ${hint}: `, (answer) => {
|
|
165
|
+
rl.close();
|
|
166
|
+
const a = answer.toLowerCase().trim();
|
|
167
|
+
if (a === '') resolve(defaultVal);
|
|
168
|
+
else if (a === 'y' || a === 'yes') resolve(true);
|
|
169
|
+
else if (a === 'n' || a === 'no') resolve(false);
|
|
170
|
+
else resolve(defaultVal);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
59
173
|
|
|
60
174
|
/**
|
|
61
|
-
* Number input
|
|
175
|
+
* Number input
|
|
62
176
|
*/
|
|
63
|
-
const numberInput =
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (n > max) return `Maximum is ${max}`;
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
177
|
+
const numberInput = (message, defaultVal = 1, min = 1, max = 1000) => new Promise(resolve => {
|
|
178
|
+
const rl = createRL();
|
|
179
|
+
rl.question(`${message} (${defaultVal}): `, (answer) => {
|
|
180
|
+
rl.close();
|
|
181
|
+
const n = parseInt(answer) || defaultVal;
|
|
182
|
+
if (n < min) resolve(min);
|
|
183
|
+
else if (n > max) resolve(max);
|
|
184
|
+
else resolve(n);
|
|
74
185
|
});
|
|
75
|
-
|
|
76
|
-
return parseInt(result);
|
|
77
|
-
};
|
|
186
|
+
});
|
|
78
187
|
|
|
79
188
|
module.exports = {
|
|
80
189
|
waitForEnter,
|
|
@@ -82,6 +191,5 @@ module.exports = {
|
|
|
82
191
|
textInput,
|
|
83
192
|
passwordInput,
|
|
84
193
|
confirmPrompt,
|
|
85
|
-
numberInput
|
|
86
|
-
isCancel
|
|
194
|
+
numberInput
|
|
87
195
|
};
|