hedgequantx 1.5.7 → 1.5.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 -1
- package/src/menus/connect.js +48 -67
- package/src/menus/dashboard.js +65 -149
- package/src/services/projectx/index.js +47 -2
- package/src/services/rithmic/index.js +51 -13
package/package.json
CHANGED
package/src/menus/connect.js
CHANGED
|
@@ -65,9 +65,8 @@ const loginPrompt = async (propfirmName) => {
|
|
|
65
65
|
const projectXMenu = async () => {
|
|
66
66
|
const propfirms = getPropFirmsByPlatform('ProjectX');
|
|
67
67
|
const boxWidth = getLogoWidth();
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const colWidth = Math.floor(innerWidth / numCols);
|
|
68
|
+
const W = boxWidth - 2; // Inner width
|
|
69
|
+
const col1Width = Math.floor(W / 2);
|
|
71
70
|
|
|
72
71
|
// Build numbered list
|
|
73
72
|
const numbered = propfirms.map((pf, i) => ({
|
|
@@ -78,61 +77,50 @@ const projectXMenu = async () => {
|
|
|
78
77
|
|
|
79
78
|
// PropFirm selection box
|
|
80
79
|
console.log();
|
|
81
|
-
console.log(chalk.cyan('╔' + '═'.repeat(
|
|
82
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM',
|
|
83
|
-
console.log(chalk.cyan('
|
|
80
|
+
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
81
|
+
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (ProjectX)', W)) + chalk.cyan('║'));
|
|
82
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
84
83
|
|
|
85
|
-
// Display in
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
// Display in 2 columns
|
|
85
|
+
const menuRow = (left, right) => {
|
|
86
|
+
const leftPlain = left ? left.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
87
|
+
const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
88
|
+
const leftPadded = ' ' + (left || '') + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
89
|
+
const rightPadded = (right || '') + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
90
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
91
|
+
};
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
|
|
98
|
-
const textLen = 4 + 1 + item.name.length; // [XX] + space + name
|
|
99
|
-
const padding = colWidth - textLen - 2;
|
|
100
|
-
line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
|
|
101
|
-
} else {
|
|
102
|
-
line += ' '.repeat(colWidth);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// Adjust for exact width
|
|
106
|
-
const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
107
|
-
const adjust = innerWidth - lineLen;
|
|
108
|
-
console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
|
|
93
|
+
// Display propfirms in 2 columns
|
|
94
|
+
for (let i = 0; i < numbered.length; i += 2) {
|
|
95
|
+
const left = numbered[i];
|
|
96
|
+
const right = numbered[i + 1];
|
|
97
|
+
const leftText = chalk.cyan(`[${left.num.toString().padStart(2, ' ')}]`) + ' ' + chalk.white(left.name);
|
|
98
|
+
const rightText = right ? chalk.cyan(`[${right.num.toString().padStart(2, ' ')}]`) + ' ' + chalk.white(right.name) : '';
|
|
99
|
+
menuRow(leftText, rightText);
|
|
109
100
|
}
|
|
110
101
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
console.log(chalk.cyan('║') +
|
|
115
|
-
console.log(chalk.cyan('╚' + '═'.repeat(
|
|
102
|
+
// Back option
|
|
103
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
104
|
+
const backLine = ' ' + chalk.red('[X] Back') + ' '.repeat(W - 10);
|
|
105
|
+
console.log(chalk.cyan('║') + backLine + chalk.cyan('║'));
|
|
106
|
+
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
116
107
|
console.log();
|
|
117
108
|
|
|
118
|
-
const validInputs = numbered.map(n => n.num.toString());
|
|
119
|
-
validInputs.push('x', 'X');
|
|
120
|
-
|
|
121
109
|
const { action } = await inquirer.prompt([
|
|
122
110
|
{
|
|
123
111
|
type: 'input',
|
|
124
112
|
name: 'action',
|
|
125
|
-
message: chalk.cyan(
|
|
126
|
-
|
|
127
|
-
if (validInputs.includes(input)) return true;
|
|
128
|
-
return `Please enter 1-${numbered.length} or X`;
|
|
129
|
-
}
|
|
113
|
+
message: chalk.cyan('Select:'),
|
|
114
|
+
prefix: ''
|
|
130
115
|
}
|
|
131
116
|
]);
|
|
132
117
|
|
|
133
|
-
|
|
118
|
+
const input = (action || '').toLowerCase().trim();
|
|
119
|
+
if (input === 'x') return null;
|
|
120
|
+
|
|
121
|
+
const selectedIdx = parseInt(input) - 1;
|
|
122
|
+
if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= numbered.length) return null;
|
|
134
123
|
|
|
135
|
-
const selectedIdx = parseInt(action) - 1;
|
|
136
124
|
const selectedPropfirm = numbered[selectedIdx];
|
|
137
125
|
|
|
138
126
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
@@ -345,53 +333,46 @@ const tradovateMenu = async () => {
|
|
|
345
333
|
*/
|
|
346
334
|
const addPropAccountMenu = async () => {
|
|
347
335
|
const boxWidth = getLogoWidth();
|
|
348
|
-
const
|
|
349
|
-
const col1Width = Math.floor(
|
|
350
|
-
const col2Width = innerWidth - col1Width;
|
|
336
|
+
const W = boxWidth - 2; // Inner width
|
|
337
|
+
const col1Width = Math.floor(W / 2);
|
|
351
338
|
|
|
352
339
|
console.log();
|
|
353
|
-
console.log(chalk.cyan('╔' + '═'.repeat(
|
|
354
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('ADD PROP ACCOUNT',
|
|
355
|
-
console.log(chalk.cyan('╠' + '═'.repeat(
|
|
340
|
+
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
341
|
+
console.log(chalk.cyan('║') + chalk.white.bold(centerText('ADD PROP ACCOUNT', W)) + chalk.cyan('║'));
|
|
342
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
356
343
|
|
|
357
344
|
const menuRow = (left, right) => {
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
const rightPad = col2Width - rightLen;
|
|
364
|
-
console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
|
|
345
|
+
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
346
|
+
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
347
|
+
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
348
|
+
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
349
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
365
350
|
};
|
|
366
351
|
|
|
367
352
|
menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
|
|
368
353
|
menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Back'));
|
|
369
354
|
|
|
370
|
-
console.log(chalk.cyan('╚' + '═'.repeat(
|
|
355
|
+
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
371
356
|
console.log();
|
|
372
357
|
|
|
373
358
|
const { action } = await inquirer.prompt([
|
|
374
359
|
{
|
|
375
360
|
type: 'input',
|
|
376
361
|
name: 'action',
|
|
377
|
-
message: chalk.cyan('
|
|
378
|
-
|
|
379
|
-
const valid = ['1', '2', '3', 'x', 'X'];
|
|
380
|
-
if (valid.includes(input)) return true;
|
|
381
|
-
return 'Please enter 1, 2, 3 or X';
|
|
382
|
-
}
|
|
362
|
+
message: chalk.cyan('Select:'),
|
|
363
|
+
prefix: ''
|
|
383
364
|
}
|
|
384
365
|
]);
|
|
385
366
|
|
|
367
|
+
const input = (action || '').toLowerCase().trim();
|
|
386
368
|
const actionMap = {
|
|
387
369
|
'1': 'projectx',
|
|
388
370
|
'2': 'rithmic',
|
|
389
371
|
'3': 'tradovate',
|
|
390
|
-
'x': null
|
|
391
|
-
'X': null
|
|
372
|
+
'x': null
|
|
392
373
|
};
|
|
393
374
|
|
|
394
|
-
return actionMap[
|
|
375
|
+
return actionMap[input] || null;
|
|
395
376
|
};
|
|
396
377
|
|
|
397
378
|
module.exports = {
|
package/src/menus/dashboard.js
CHANGED
|
@@ -19,115 +19,78 @@ const dashboardMenu = async (service) => {
|
|
|
19
19
|
// Ensure stdin is ready for prompts
|
|
20
20
|
prepareStdin();
|
|
21
21
|
|
|
22
|
-
const user = service.user;
|
|
23
22
|
const boxWidth = getLogoWidth();
|
|
24
|
-
const W = boxWidth - 2; //
|
|
23
|
+
const W = boxWidth - 2; // Inner width (without borders)
|
|
25
24
|
|
|
26
|
-
// Helper to
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
// Helper to create a line that fits exactly in the box
|
|
26
|
+
const makeLine = (content, align = 'left') => {
|
|
27
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
28
|
+
const padding = W - plainLen;
|
|
29
|
+
if (align === 'center') {
|
|
30
|
+
const leftPad = Math.floor(padding / 2);
|
|
31
|
+
const rightPad = padding - leftPad;
|
|
32
|
+
return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + chalk.cyan('║');
|
|
33
|
+
}
|
|
34
|
+
return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
// Dashboard box header
|
|
38
38
|
console.log();
|
|
39
39
|
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
40
|
-
console.log(
|
|
40
|
+
console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
|
|
41
41
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// Show connected propfirms centered on one line (max 3)
|
|
44
44
|
const allConns = connections.getAll();
|
|
45
45
|
if (allConns.length > 0) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Calculate box width based on number of connections (max 3)
|
|
51
|
-
const numBoxes = Math.min(allConns.length, maxPerRow);
|
|
52
|
-
const totalGaps = (numBoxes - 1) * gap;
|
|
53
|
-
const connBoxWidth = Math.floor((W - totalGaps - 2) / numBoxes); // -2 for outer padding
|
|
54
|
-
|
|
55
|
-
// Process connections in rows of 3
|
|
56
|
-
for (let rowStart = 0; rowStart < allConns.length; rowStart += maxPerRow) {
|
|
57
|
-
const rowConns = allConns.slice(rowStart, rowStart + maxPerRow);
|
|
58
|
-
const numInRow = rowConns.length;
|
|
59
|
-
const rowBoxWidth = Math.floor((W - (numInRow - 1) * gap - 2) / numInRow);
|
|
60
|
-
|
|
61
|
-
// Top border of boxes
|
|
62
|
-
let topLine = ' ';
|
|
63
|
-
for (let i = 0; i < numInRow; i++) {
|
|
64
|
-
topLine += '┌' + '─'.repeat(rowBoxWidth - 2) + '┐';
|
|
65
|
-
if (i < numInRow - 1) topLine += ' '.repeat(gap);
|
|
66
|
-
}
|
|
67
|
-
const topPad = W - topLine.length;
|
|
68
|
-
console.log(chalk.cyan('║') + chalk.green(topLine) + ' '.repeat(Math.max(0, topPad)) + chalk.cyan('║'));
|
|
69
|
-
|
|
70
|
-
// Content of boxes
|
|
71
|
-
let contentLine = ' ';
|
|
72
|
-
for (let i = 0; i < numInRow; i++) {
|
|
73
|
-
const connText = rowConns[i].propfirm || rowConns[i].type || 'Connected';
|
|
74
|
-
const truncated = connText.length > rowBoxWidth - 4 ? connText.slice(0, rowBoxWidth - 7) + '...' : connText;
|
|
75
|
-
const innerWidth = rowBoxWidth - 4; // -2 for borders, -2 for padding
|
|
76
|
-
const textPad = Math.floor((innerWidth - truncated.length) / 2);
|
|
77
|
-
const textPadRight = innerWidth - truncated.length - textPad;
|
|
78
|
-
contentLine += '│ ' + ' '.repeat(textPad) + truncated + ' '.repeat(textPadRight) + ' │';
|
|
79
|
-
if (i < numInRow - 1) contentLine += ' '.repeat(gap);
|
|
80
|
-
}
|
|
81
|
-
const contentPad = W - contentLine.length;
|
|
82
|
-
console.log(chalk.cyan('║') + chalk.green(contentLine) + ' '.repeat(Math.max(0, contentPad)) + chalk.cyan('║'));
|
|
83
|
-
|
|
84
|
-
// Bottom border of boxes
|
|
85
|
-
let bottomLine = ' ';
|
|
86
|
-
for (let i = 0; i < numInRow; i++) {
|
|
87
|
-
bottomLine += '└' + '─'.repeat(rowBoxWidth - 2) + '┘';
|
|
88
|
-
if (i < numInRow - 1) bottomLine += ' '.repeat(gap);
|
|
89
|
-
}
|
|
90
|
-
const bottomPad = W - bottomLine.length;
|
|
91
|
-
console.log(chalk.cyan('║') + chalk.green(bottomLine) + ' '.repeat(Math.max(0, bottomPad)) + chalk.cyan('║'));
|
|
92
|
-
}
|
|
46
|
+
const propfirms = allConns.slice(0, 3).map(c => c.propfirm || c.type || 'Connected');
|
|
47
|
+
const propfirmText = propfirms.map(p => chalk.green('● ') + chalk.white(p)).join(' ');
|
|
48
|
+
console.log(makeLine(propfirmText, 'center'));
|
|
93
49
|
}
|
|
94
50
|
|
|
95
51
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
96
52
|
|
|
97
53
|
// Menu options in 2 columns
|
|
98
54
|
const col1Width = Math.floor(W / 2);
|
|
99
|
-
const col2Width = W - col1Width;
|
|
100
55
|
|
|
101
56
|
const menuRow = (left, right) => {
|
|
102
57
|
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
103
|
-
const rightPlain = right
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
console.log(chalk.cyan('║') +
|
|
58
|
+
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
59
|
+
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
60
|
+
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
61
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
107
62
|
};
|
|
108
63
|
|
|
64
|
+
// Display menu items in 2 columns inside the box
|
|
65
|
+
menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
|
|
66
|
+
menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.magenta('[A] Algo-Trading'));
|
|
67
|
+
menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
|
|
68
|
+
|
|
109
69
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
110
70
|
console.log();
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
const {
|
|
71
|
+
|
|
72
|
+
// Input prompt
|
|
73
|
+
const { choice } = await inquirer.prompt([
|
|
114
74
|
{
|
|
115
|
-
type: '
|
|
116
|
-
name: '
|
|
117
|
-
message: chalk.cyan('Select
|
|
118
|
-
|
|
119
|
-
{ name: chalk.cyan('[1] View Accounts'), value: 'accounts' },
|
|
120
|
-
{ name: chalk.cyan('[2] View Stats'), value: 'stats' },
|
|
121
|
-
{ name: chalk.cyan('[+] Add Prop-Account'), value: 'add_prop_account' },
|
|
122
|
-
{ name: chalk.magenta('[A] Algo-Trading'), value: 'algotrading' },
|
|
123
|
-
{ name: chalk.yellow('[U] Update HQX'), value: 'update' },
|
|
124
|
-
{ name: chalk.red('[X] Disconnect'), value: 'disconnect' }
|
|
125
|
-
],
|
|
126
|
-
loop: false
|
|
75
|
+
type: 'input',
|
|
76
|
+
name: 'choice',
|
|
77
|
+
message: chalk.cyan('Select:'),
|
|
78
|
+
prefix: ''
|
|
127
79
|
}
|
|
128
80
|
]);
|
|
129
|
-
|
|
130
|
-
|
|
81
|
+
|
|
82
|
+
// Map input to action
|
|
83
|
+
const input = (choice || '').toString().toLowerCase().trim();
|
|
84
|
+
const actionMap = {
|
|
85
|
+
'1': 'accounts',
|
|
86
|
+
'2': 'stats',
|
|
87
|
+
'+': 'add_prop_account',
|
|
88
|
+
'a': 'algotrading',
|
|
89
|
+
'u': 'update',
|
|
90
|
+
'x': 'disconnect'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return actionMap[input] || null;
|
|
131
94
|
};
|
|
132
95
|
|
|
133
96
|
/**
|
|
@@ -188,43 +151,19 @@ const handleUpdate = async () => {
|
|
|
188
151
|
|
|
189
152
|
// Compare versions
|
|
190
153
|
if (currentVersion === latestVersion) {
|
|
191
|
-
spinner.succeed(
|
|
192
|
-
console.log();
|
|
193
|
-
console.log(chalk.green(` You have the latest version: v${currentVersion}`));
|
|
154
|
+
spinner.succeed(`Already up to date! (v${currentVersion})`);
|
|
194
155
|
console.log();
|
|
195
|
-
await
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Ask user before updating
|
|
200
|
-
spinner.stop();
|
|
201
|
-
console.log();
|
|
202
|
-
console.log(chalk.cyan(` Current version: v${currentVersion}`));
|
|
203
|
-
console.log(chalk.green(` Latest version: v${latestVersion}`));
|
|
204
|
-
console.log();
|
|
205
|
-
|
|
206
|
-
prepareStdin();
|
|
207
|
-
const { confirm } = await inquirer.prompt([{
|
|
208
|
-
type: 'confirm',
|
|
209
|
-
name: 'confirm',
|
|
210
|
-
message: 'Do you want to update now?',
|
|
211
|
-
default: true
|
|
212
|
-
}]);
|
|
213
|
-
|
|
214
|
-
if (!confirm) {
|
|
215
|
-
console.log(chalk.gray(' Update cancelled'));
|
|
216
|
-
console.log();
|
|
217
|
-
await waitForEnter();
|
|
156
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
218
157
|
return;
|
|
219
158
|
}
|
|
220
159
|
|
|
221
|
-
//
|
|
222
|
-
spinner =
|
|
160
|
+
// Show version info and update automatically
|
|
161
|
+
spinner.text = `Updating v${currentVersion} → v${latestVersion}...`;
|
|
223
162
|
|
|
224
163
|
try {
|
|
225
164
|
execSync('npm install -g hedgequantx@latest 2>/dev/null', {
|
|
226
165
|
stdio: 'pipe',
|
|
227
|
-
timeout: 120000,
|
|
166
|
+
timeout: 120000,
|
|
228
167
|
encoding: 'utf8'
|
|
229
168
|
});
|
|
230
169
|
} catch (e) {
|
|
@@ -233,52 +172,29 @@ const handleUpdate = async () => {
|
|
|
233
172
|
console.log(chalk.yellow(' Try manually:'));
|
|
234
173
|
console.log(chalk.white(' npm install -g hedgequantx@latest'));
|
|
235
174
|
console.log();
|
|
236
|
-
if (e.message) {
|
|
237
|
-
console.log(chalk.gray(` Error: ${e.message.substring(0, 100)}`));
|
|
238
|
-
console.log();
|
|
239
|
-
}
|
|
240
175
|
await waitForEnter();
|
|
241
176
|
return;
|
|
242
177
|
}
|
|
243
178
|
|
|
244
|
-
spinner.succeed(
|
|
179
|
+
spinner.succeed(`Updated: v${currentVersion} → v${latestVersion}`);
|
|
245
180
|
console.log();
|
|
246
|
-
console.log(chalk.
|
|
181
|
+
console.log(chalk.cyan(' Restarting HedgeQuantX CLI...'));
|
|
247
182
|
console.log();
|
|
248
183
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
const { restart } = await inquirer.prompt([{
|
|
252
|
-
type: 'confirm',
|
|
253
|
-
name: 'restart',
|
|
254
|
-
message: 'Restart HQX now?',
|
|
255
|
-
default: true
|
|
256
|
-
}]);
|
|
184
|
+
// Auto restart after 2 seconds
|
|
185
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
257
186
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
stdio: 'inherit',
|
|
270
|
-
detached: true,
|
|
271
|
-
shell: true
|
|
272
|
-
});
|
|
273
|
-
child.unref();
|
|
274
|
-
process.exit(0);
|
|
275
|
-
} catch (e) {
|
|
276
|
-
console.log(chalk.yellow(' Could not auto-restart. Please run: hedgequantx'));
|
|
277
|
-
console.log();
|
|
278
|
-
await waitForEnter();
|
|
279
|
-
}
|
|
280
|
-
} else {
|
|
281
|
-
console.log(chalk.gray(' Run "hedgequantx" to use the new version'));
|
|
187
|
+
// Restart the CLI
|
|
188
|
+
try {
|
|
189
|
+
const child = spawn('hedgequantx', [], {
|
|
190
|
+
stdio: 'inherit',
|
|
191
|
+
detached: true,
|
|
192
|
+
shell: true
|
|
193
|
+
});
|
|
194
|
+
child.unref();
|
|
195
|
+
process.exit(0);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
console.log(chalk.yellow(' Could not auto-restart. Please run: hedgequantx'));
|
|
282
198
|
console.log();
|
|
283
199
|
await waitForEnter();
|
|
284
200
|
}
|
|
@@ -449,7 +449,7 @@ class ProjectXService {
|
|
|
449
449
|
async getContracts() {
|
|
450
450
|
try {
|
|
451
451
|
// Search for popular futures symbols
|
|
452
|
-
const symbols = ['ES', 'NQ', 'MES', 'MNQ', 'CL', 'GC', 'RTY', 'YM'];
|
|
452
|
+
const symbols = ['ES', 'NQ', 'MES', 'MNQ', 'CL', 'GC', 'RTY', 'YM', 'SI', 'ZB', 'ZN', 'NG'];
|
|
453
453
|
const allContracts = [];
|
|
454
454
|
|
|
455
455
|
for (const sym of symbols) {
|
|
@@ -461,7 +461,12 @@ class ProjectXService {
|
|
|
461
461
|
const contracts = response.data.contracts || response.data || [];
|
|
462
462
|
// Take first contract for each symbol (front month)
|
|
463
463
|
if (contracts.length > 0) {
|
|
464
|
-
|
|
464
|
+
const contract = contracts[0];
|
|
465
|
+
// Ensure name is set properly
|
|
466
|
+
if (!contract.name || contract.name === contract.symbol) {
|
|
467
|
+
contract.name = this._getContractDisplayName(contract.symbol) || contract.symbol;
|
|
468
|
+
}
|
|
469
|
+
allContracts.push(contract);
|
|
465
470
|
}
|
|
466
471
|
}
|
|
467
472
|
}
|
|
@@ -472,6 +477,46 @@ class ProjectXService {
|
|
|
472
477
|
}
|
|
473
478
|
}
|
|
474
479
|
|
|
480
|
+
/**
|
|
481
|
+
* Get display name for contract symbol
|
|
482
|
+
*/
|
|
483
|
+
_getContractDisplayName(symbol) {
|
|
484
|
+
const baseSymbol = symbol.replace(/[A-Z][0-9]$/, '').replace(/[0-9]+$/, '');
|
|
485
|
+
const names = {
|
|
486
|
+
'ES': 'E-mini S&P 500',
|
|
487
|
+
'NQ': 'E-mini NASDAQ-100',
|
|
488
|
+
'MES': 'Micro E-mini S&P 500',
|
|
489
|
+
'MNQ': 'Micro E-mini NASDAQ-100',
|
|
490
|
+
'RTY': 'E-mini Russell 2000',
|
|
491
|
+
'M2K': 'Micro E-mini Russell 2000',
|
|
492
|
+
'YM': 'E-mini Dow Jones',
|
|
493
|
+
'MYM': 'Micro E-mini Dow Jones',
|
|
494
|
+
'CL': 'Crude Oil',
|
|
495
|
+
'MCL': 'Micro Crude Oil',
|
|
496
|
+
'GC': 'Gold',
|
|
497
|
+
'MGC': 'Micro Gold',
|
|
498
|
+
'SI': 'Silver',
|
|
499
|
+
'SIL': 'Micro Silver',
|
|
500
|
+
'NG': 'Natural Gas',
|
|
501
|
+
'ZB': '30-Year Treasury Bond',
|
|
502
|
+
'ZN': '10-Year Treasury Note',
|
|
503
|
+
'ZF': '5-Year Treasury Note',
|
|
504
|
+
'ZC': 'Corn',
|
|
505
|
+
'ZS': 'Soybeans',
|
|
506
|
+
'ZW': 'Wheat',
|
|
507
|
+
'6E': 'Euro FX',
|
|
508
|
+
'6B': 'British Pound',
|
|
509
|
+
'6J': 'Japanese Yen',
|
|
510
|
+
};
|
|
511
|
+
const baseName = names[baseSymbol];
|
|
512
|
+
if (baseName) {
|
|
513
|
+
// Extract month/year from symbol
|
|
514
|
+
const monthYear = symbol.slice(baseSymbol.length);
|
|
515
|
+
return `${baseName} (${monthYear})`;
|
|
516
|
+
}
|
|
517
|
+
return symbol;
|
|
518
|
+
}
|
|
519
|
+
|
|
475
520
|
async searchContracts(searchText) {
|
|
476
521
|
try {
|
|
477
522
|
const response = await this._request(
|
|
@@ -211,21 +211,59 @@ class RithmicService extends EventEmitter {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
// All available contracts for Rithmic
|
|
214
|
+
// TODO: Fetch from TICKER_PLANT API instead of static list
|
|
214
215
|
_getAvailableContracts() {
|
|
216
|
+
// Current front-month contracts (update monthly)
|
|
215
217
|
return [
|
|
216
|
-
|
|
217
|
-
{ symbol: '
|
|
218
|
-
{ symbol: '
|
|
219
|
-
{ symbol: '
|
|
220
|
-
{ symbol: '
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
{ symbol: '
|
|
224
|
-
{ symbol: '
|
|
225
|
-
{ symbol: '
|
|
226
|
-
{ symbol: '
|
|
227
|
-
|
|
228
|
-
|
|
218
|
+
// Index Futures
|
|
219
|
+
{ symbol: 'ESH5', name: 'E-mini S&P 500 (Mar 25)', exchange: 'CME', group: 'Index' },
|
|
220
|
+
{ symbol: 'NQH5', name: 'E-mini NASDAQ-100 (Mar 25)', exchange: 'CME', group: 'Index' },
|
|
221
|
+
{ symbol: 'RTYH5', name: 'E-mini Russell 2000 (Mar 25)', exchange: 'CME', group: 'Index' },
|
|
222
|
+
{ symbol: 'YMH5', name: 'E-mini Dow Jones (Mar 25)', exchange: 'CBOT', group: 'Index' },
|
|
223
|
+
|
|
224
|
+
// Micro Index Futures
|
|
225
|
+
{ symbol: 'MESH5', name: 'Micro E-mini S&P 500 (Mar 25)', exchange: 'CME', group: 'Micro Index' },
|
|
226
|
+
{ symbol: 'MNQH5', name: 'Micro E-mini NASDAQ-100 (Mar 25)', exchange: 'CME', group: 'Micro Index' },
|
|
227
|
+
{ symbol: 'M2KH5', name: 'Micro E-mini Russell 2000 (Mar 25)', exchange: 'CME', group: 'Micro Index' },
|
|
228
|
+
{ symbol: 'MYMH5', name: 'Micro E-mini Dow Jones (Mar 25)', exchange: 'CBOT', group: 'Micro Index' },
|
|
229
|
+
|
|
230
|
+
// Energy Futures
|
|
231
|
+
{ symbol: 'CLG5', name: 'Crude Oil (Feb 25)', exchange: 'NYMEX', group: 'Energy' },
|
|
232
|
+
{ symbol: 'CLH5', name: 'Crude Oil (Mar 25)', exchange: 'NYMEX', group: 'Energy' },
|
|
233
|
+
{ symbol: 'NGG5', name: 'Natural Gas (Feb 25)', exchange: 'NYMEX', group: 'Energy' },
|
|
234
|
+
{ symbol: 'NGH5', name: 'Natural Gas (Mar 25)', exchange: 'NYMEX', group: 'Energy' },
|
|
235
|
+
|
|
236
|
+
// Micro Energy
|
|
237
|
+
{ symbol: 'MCLG5', name: 'Micro Crude Oil (Feb 25)', exchange: 'NYMEX', group: 'Micro Energy' },
|
|
238
|
+
{ symbol: 'MCLH5', name: 'Micro Crude Oil (Mar 25)', exchange: 'NYMEX', group: 'Micro Energy' },
|
|
239
|
+
|
|
240
|
+
// Metals Futures
|
|
241
|
+
{ symbol: 'GCG5', name: 'Gold (Feb 25)', exchange: 'COMEX', group: 'Metals' },
|
|
242
|
+
{ symbol: 'GCJ5', name: 'Gold (Apr 25)', exchange: 'COMEX', group: 'Metals' },
|
|
243
|
+
{ symbol: 'SIH5', name: 'Silver (Mar 25)', exchange: 'COMEX', group: 'Metals' },
|
|
244
|
+
{ symbol: 'HGH5', name: 'Copper (Mar 25)', exchange: 'COMEX', group: 'Metals' },
|
|
245
|
+
|
|
246
|
+
// Micro Metals
|
|
247
|
+
{ symbol: 'MGCG5', name: 'Micro Gold (Feb 25)', exchange: 'COMEX', group: 'Micro Metals' },
|
|
248
|
+
{ symbol: 'MGCJ5', name: 'Micro Gold (Apr 25)', exchange: 'COMEX', group: 'Micro Metals' },
|
|
249
|
+
{ symbol: 'SILU5', name: 'Micro Silver (Sep 25)', exchange: 'COMEX', group: 'Micro Metals' },
|
|
250
|
+
|
|
251
|
+
// Treasury Futures
|
|
252
|
+
{ symbol: 'ZBH5', name: '30-Year US Treasury Bond (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
|
|
253
|
+
{ symbol: 'ZNH5', name: '10-Year US Treasury Note (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
|
|
254
|
+
{ symbol: 'ZFH5', name: '5-Year US Treasury Note (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
|
|
255
|
+
{ symbol: 'ZTH5', name: '2-Year US Treasury Note (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
|
|
256
|
+
|
|
257
|
+
// Agriculture Futures
|
|
258
|
+
{ symbol: 'ZCH5', name: 'Corn (Mar 25)', exchange: 'CBOT', group: 'Agriculture' },
|
|
259
|
+
{ symbol: 'ZSH5', name: 'Soybeans (Mar 25)', exchange: 'CBOT', group: 'Agriculture' },
|
|
260
|
+
{ symbol: 'ZWH5', name: 'Wheat (Mar 25)', exchange: 'CBOT', group: 'Agriculture' },
|
|
261
|
+
|
|
262
|
+
// Currency Futures
|
|
263
|
+
{ symbol: '6EH5', name: 'Euro FX (Mar 25)', exchange: 'CME', group: 'Currency' },
|
|
264
|
+
{ symbol: '6BH5', name: 'British Pound (Mar 25)', exchange: 'CME', group: 'Currency' },
|
|
265
|
+
{ symbol: '6JH5', name: 'Japanese Yen (Mar 25)', exchange: 'CME', group: 'Currency' },
|
|
266
|
+
{ symbol: '6AH5', name: 'Australian Dollar (Mar 25)', exchange: 'CME', group: 'Currency' },
|
|
229
267
|
];
|
|
230
268
|
}
|
|
231
269
|
|