hedgequantx 2.9.19 → 2.9.21
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/app.js +42 -64
- package/src/lib/m/hqx-2b.js +7 -0
- package/src/lib/m/index.js +138 -0
- package/src/lib/m/ultra-scalping.js +7 -0
- package/src/menus/connect.js +14 -17
- package/src/menus/dashboard.js +58 -76
- package/src/pages/accounts.js +38 -49
- package/src/pages/algo/copy-trading.js +546 -178
- package/src/pages/algo/index.js +18 -75
- package/src/pages/algo/one-account.js +322 -57
- package/src/pages/algo/ui.js +15 -15
- package/src/pages/orders.js +19 -22
- package/src/pages/positions.js +19 -22
- package/src/pages/stats/index.js +15 -16
- package/src/pages/user.js +7 -11
- package/src/services/ai-supervision/health.js +35 -47
- package/src/services/index.js +1 -9
- package/src/services/rithmic/accounts.js +8 -6
- package/src/ui/box.js +9 -5
- package/src/ui/index.js +5 -18
- package/src/ui/menu.js +4 -4
- package/src/pages/ai-agents-ui.js +0 -388
- package/src/pages/ai-agents.js +0 -494
- package/src/pages/ai-models.js +0 -389
- package/src/pages/algo/algo-executor.js +0 -307
- package/src/pages/algo/copy-executor.js +0 -331
- package/src/pages/algo/custom-strategy.js +0 -313
- package/src/services/ai-supervision/consensus.js +0 -284
- package/src/services/ai-supervision/context.js +0 -275
- package/src/services/ai-supervision/directive.js +0 -167
- package/src/services/ai-supervision/index.js +0 -359
- package/src/services/ai-supervision/parser.js +0 -278
- package/src/services/ai-supervision/symbols.js +0 -259
- package/src/services/cliproxy/index.js +0 -256
- package/src/services/cliproxy/installer.js +0 -111
- package/src/services/cliproxy/manager.js +0 -387
- package/src/services/llmproxy/index.js +0 -166
- package/src/services/llmproxy/manager.js +0 -411
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -7,7 +7,7 @@ const chalk = require('chalk');
|
|
|
7
7
|
const ora = require('ora');
|
|
8
8
|
|
|
9
9
|
const { connections } = require('./services');
|
|
10
|
-
const { getLogoWidth, centerText, prepareStdin
|
|
10
|
+
const { getLogoWidth, centerText, prepareStdin } = require('./ui');
|
|
11
11
|
const { logger, prompts } = require('./utils');
|
|
12
12
|
const { setCachedStats, clearCachedStats } = require('./services/stats-cache');
|
|
13
13
|
|
|
@@ -17,7 +17,6 @@ const log = logger.scope('App');
|
|
|
17
17
|
const { showStats } = require('./pages/stats');
|
|
18
18
|
const { showAccounts } = require('./pages/accounts');
|
|
19
19
|
const { algoTradingMenu } = require('./pages/algo');
|
|
20
|
-
const { aiAgentsMenu, getActiveAgentCount } = require('./pages/ai-agents');
|
|
21
20
|
|
|
22
21
|
// Menus
|
|
23
22
|
const { rithmicMenu, dashboardMenu, handleUpdate } = require('./menus');
|
|
@@ -30,7 +29,7 @@ let currentService = null;
|
|
|
30
29
|
|
|
31
30
|
const restoreTerminal = () => {
|
|
32
31
|
try {
|
|
33
|
-
|
|
32
|
+
process.stdout.write('\x1B[?1049l');
|
|
34
33
|
process.stdout.write('\x1B[?25h');
|
|
35
34
|
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
36
35
|
process.stdin.setRawMode(false);
|
|
@@ -42,15 +41,7 @@ const restoreTerminal = () => {
|
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
process.on('exit', restoreTerminal);
|
|
45
|
-
process.on('SIGINT', () => {
|
|
46
|
-
restoreTerminal();
|
|
47
|
-
// Draw bottom border before exit
|
|
48
|
-
const termWidth = process.stdout.columns || 100;
|
|
49
|
-
const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
50
|
-
console.log(chalk.cyan('\n╚' + '═'.repeat(boxWidth - 2) + '╝'));
|
|
51
|
-
console.log(chalk.gray('GOODBYE!'));
|
|
52
|
-
process.exit(0);
|
|
53
|
-
});
|
|
44
|
+
process.on('SIGINT', () => { restoreTerminal(); process.exit(0); });
|
|
54
45
|
process.on('SIGTERM', () => { restoreTerminal(); process.exit(0); });
|
|
55
46
|
|
|
56
47
|
process.on('uncaughtException', (err) => {
|
|
@@ -101,7 +92,6 @@ const refreshStats = async () => {
|
|
|
101
92
|
balance: hasBalanceData ? totalBalance : null,
|
|
102
93
|
pnl: hasPnlData ? totalPnl : null,
|
|
103
94
|
pnlPercent: null,
|
|
104
|
-
agents: getActiveAgentCount(),
|
|
105
95
|
});
|
|
106
96
|
} catch (err) {
|
|
107
97
|
log.warn('Failed to refresh stats', { error: err.message });
|
|
@@ -111,7 +101,7 @@ const refreshStats = async () => {
|
|
|
111
101
|
// ==================== BANNER ====================
|
|
112
102
|
|
|
113
103
|
const banner = async () => {
|
|
114
|
-
|
|
104
|
+
console.clear();
|
|
115
105
|
|
|
116
106
|
const termWidth = process.stdout.columns || 100;
|
|
117
107
|
const isMobile = termWidth < 60;
|
|
@@ -133,11 +123,8 @@ const banner = async () => {
|
|
|
133
123
|
|
|
134
124
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
135
125
|
|
|
136
|
-
const tagline = isMobile ? `HQX
|
|
137
|
-
console.log(chalk.cyan('║') + chalk.
|
|
138
|
-
|
|
139
|
-
// ALWAYS close the banner
|
|
140
|
-
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
126
|
+
const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
|
|
127
|
+
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
141
128
|
};
|
|
142
129
|
|
|
143
130
|
const getFullLogo = () => [
|
|
@@ -158,37 +145,39 @@ const getMobileLogo = () => [
|
|
|
158
145
|
['╚═╝ ╚═╝ ╚══▀▀═╝ ', '╚═╝ ╚═╝'],
|
|
159
146
|
];
|
|
160
147
|
|
|
148
|
+
const bannerClosed = async () => {
|
|
149
|
+
await banner();
|
|
150
|
+
const termWidth = process.stdout.columns || 100;
|
|
151
|
+
const boxWidth = termWidth < 60 ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
152
|
+
console.log(chalk.cyan('╚' + '═'.repeat(boxWidth - 2) + '╝'));
|
|
153
|
+
};
|
|
154
|
+
|
|
161
155
|
// ==================== MAIN LOOP ====================
|
|
162
156
|
|
|
163
157
|
const run = async () => {
|
|
164
158
|
try {
|
|
165
159
|
log.info('Starting HQX CLI');
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const spinner = ora({ text: 'LOADING DASHBOARD...', color: 'yellow' }).start();
|
|
171
|
-
|
|
160
|
+
await bannerClosed();
|
|
161
|
+
|
|
162
|
+
// Restore session
|
|
163
|
+
const spinner = ora({ text: 'Testing connections...', color: 'yellow' }).start();
|
|
172
164
|
const restored = await connections.restoreFromStorage();
|
|
173
165
|
|
|
174
166
|
if (restored) {
|
|
167
|
+
spinner.succeed('Connected');
|
|
175
168
|
currentService = connections.getAll()[0].service;
|
|
176
169
|
await refreshStats();
|
|
177
|
-
// Store spinner globally - dashboard will stop it when ready to display
|
|
178
|
-
global.__hqxSpinner = spinner;
|
|
179
170
|
} else {
|
|
180
|
-
spinner.
|
|
181
|
-
global.__hqxSpinner = null;
|
|
171
|
+
spinner.info('No saved connections');
|
|
182
172
|
}
|
|
183
173
|
|
|
184
174
|
// Main loop
|
|
185
175
|
while (true) {
|
|
186
176
|
try {
|
|
187
177
|
prepareStdin();
|
|
178
|
+
await banner();
|
|
188
179
|
|
|
189
180
|
if (!connections.isConnected()) {
|
|
190
|
-
// Not connected - show banner + propfirm selection
|
|
191
|
-
await banner();
|
|
192
181
|
// Not connected - show propfirm selection directly
|
|
193
182
|
const boxWidth = getLogoWidth();
|
|
194
183
|
const innerWidth = boxWidth - 2;
|
|
@@ -199,14 +188,13 @@ const run = async () => {
|
|
|
199
188
|
|
|
200
189
|
// Find max name length for alignment
|
|
201
190
|
const maxNameLen = Math.max(...numbered.map(n => n.name.length));
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
191
|
+
const colWidth = 4 + 1 + maxNameLen + 2; // [##] + space + name + gap
|
|
192
|
+
const totalContentWidth = numCols * colWidth;
|
|
193
|
+
const leftMargin = Math.max(2, Math.floor((innerWidth - totalContentWidth) / 2));
|
|
205
194
|
|
|
206
|
-
// New rectangle (banner is always closed)
|
|
207
|
-
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
208
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
|
|
209
195
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
196
|
+
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
|
|
197
|
+
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
210
198
|
|
|
211
199
|
const rows = Math.ceil(numbered.length / numCols);
|
|
212
200
|
for (let row = 0; row < rows; row++) {
|
|
@@ -223,32 +211,29 @@ const run = async () => {
|
|
|
223
211
|
}
|
|
224
212
|
}
|
|
225
213
|
|
|
226
|
-
|
|
227
|
-
let content = '';
|
|
214
|
+
let line = ' '.repeat(leftMargin);
|
|
228
215
|
for (let i = 0; i < lineParts.length; i++) {
|
|
229
216
|
if (lineParts[i]) {
|
|
230
|
-
|
|
217
|
+
line += chalk.cyan(lineParts[i].num) + ' ' + chalk.white(lineParts[i].name);
|
|
231
218
|
} else {
|
|
232
|
-
|
|
219
|
+
line += ' '.repeat(4 + 1 + maxNameLen);
|
|
233
220
|
}
|
|
234
|
-
if (i < lineParts.length - 1)
|
|
221
|
+
if (i < lineParts.length - 1) line += ' ';
|
|
235
222
|
}
|
|
236
223
|
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
const rightPad = innerWidth - contentLen - leftPad;
|
|
241
|
-
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + chalk.cyan('║'));
|
|
224
|
+
const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
225
|
+
const rightPad = Math.max(0, innerWidth - lineLen);
|
|
226
|
+
console.log(chalk.cyan('║') + line + ' '.repeat(rightPad) + chalk.cyan('║'));
|
|
242
227
|
}
|
|
243
228
|
|
|
244
229
|
console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
|
|
245
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[X]
|
|
230
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[X] Exit', innerWidth)) + chalk.cyan('║'));
|
|
246
231
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
247
232
|
|
|
248
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
233
|
+
const input = await prompts.textInput(chalk.cyan('Select number (or X):'));
|
|
249
234
|
|
|
250
235
|
if (!input || input.toLowerCase() === 'x') {
|
|
251
|
-
console.log(chalk.gray('
|
|
236
|
+
console.log(chalk.gray('Goodbye!'));
|
|
252
237
|
process.exit(0);
|
|
253
238
|
}
|
|
254
239
|
|
|
@@ -259,26 +244,26 @@ const run = async () => {
|
|
|
259
244
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
260
245
|
|
|
261
246
|
if (credentials) {
|
|
262
|
-
const spinner = ora({ text: '
|
|
247
|
+
const spinner = ora({ text: 'Connecting to Rithmic...', color: 'yellow' }).start();
|
|
263
248
|
try {
|
|
264
249
|
const { RithmicService } = require('./services/rithmic');
|
|
265
250
|
const service = new RithmicService(selectedPropfirm.key);
|
|
266
251
|
const result = await service.login(credentials.username, credentials.password);
|
|
267
252
|
|
|
268
253
|
if (result.success) {
|
|
269
|
-
spinner.text = '
|
|
254
|
+
spinner.text = 'Fetching accounts...';
|
|
270
255
|
const accResult = await service.getTradingAccounts();
|
|
271
256
|
connections.add('rithmic', service, service.propfirm.name);
|
|
272
|
-
spinner.succeed(`
|
|
257
|
+
spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
|
|
273
258
|
currentService = service;
|
|
274
259
|
await refreshStats();
|
|
275
260
|
await new Promise(r => setTimeout(r, 1500));
|
|
276
261
|
} else {
|
|
277
|
-
spinner.fail(
|
|
262
|
+
spinner.fail(result.error || 'Authentication failed');
|
|
278
263
|
await new Promise(r => setTimeout(r, 2000));
|
|
279
264
|
}
|
|
280
265
|
} catch (error) {
|
|
281
|
-
spinner.fail(`
|
|
266
|
+
spinner.fail(`Connection error: ${error.message}`);
|
|
282
267
|
await new Promise(r => setTimeout(r, 2000));
|
|
283
268
|
}
|
|
284
269
|
}
|
|
@@ -311,20 +296,13 @@ const run = async () => {
|
|
|
311
296
|
try {
|
|
312
297
|
await algoTradingMenu(currentService);
|
|
313
298
|
} catch (err) {
|
|
314
|
-
console.log(chalk.red(`
|
|
299
|
+
console.log(chalk.red(` Algo error: ${err.message}`));
|
|
315
300
|
prepareStdin();
|
|
316
301
|
}
|
|
317
302
|
break;
|
|
318
303
|
|
|
319
|
-
case 'aiagents':
|
|
320
|
-
await aiAgentsMenu();
|
|
321
|
-
break;
|
|
322
|
-
|
|
323
304
|
case 'update':
|
|
324
|
-
|
|
325
|
-
if (updateResult === 'exit') {
|
|
326
|
-
running = false;
|
|
327
|
-
}
|
|
305
|
+
await handleUpdate();
|
|
328
306
|
break;
|
|
329
307
|
|
|
330
308
|
case 'disconnect':
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy Registry - Available Trading Strategies
|
|
3
|
+
*
|
|
4
|
+
* All strategies are compiled to bytecode (.jsc) for protection.
|
|
5
|
+
* This module provides a unified interface to load strategies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// AVAILABLE STRATEGIES
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
const STRATEGIES = {
|
|
15
|
+
'ultra-scalping': {
|
|
16
|
+
id: 'ultra-scalping',
|
|
17
|
+
name: 'HQX Ultra Scalping',
|
|
18
|
+
description: '6 Mathematical Models (Z-Score, VPIN, Kyle, Kalman, Vol, OFI)',
|
|
19
|
+
version: '2.0',
|
|
20
|
+
backtest: {
|
|
21
|
+
pnl: '$2,012,373',
|
|
22
|
+
winRate: '71.1%',
|
|
23
|
+
trades: '146,685',
|
|
24
|
+
period: 'Jan 2020 - Nov 2025'
|
|
25
|
+
},
|
|
26
|
+
params: {
|
|
27
|
+
stopTicks: 8,
|
|
28
|
+
targetTicks: 16,
|
|
29
|
+
riskReward: '1:2'
|
|
30
|
+
},
|
|
31
|
+
loader: () => require('./ultra-scalping')
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
'hqx-2b': {
|
|
35
|
+
id: 'hqx-2b',
|
|
36
|
+
name: 'HQX-2B Liquidity Sweep',
|
|
37
|
+
description: '2B Pattern with Liquidity Zone Sweeps (Optimized)',
|
|
38
|
+
version: '1.0',
|
|
39
|
+
backtest: {
|
|
40
|
+
pnl: '$6,601,305',
|
|
41
|
+
winRate: '82.8%',
|
|
42
|
+
trades: '100,220',
|
|
43
|
+
period: 'Dec 2020 - Nov 2025'
|
|
44
|
+
},
|
|
45
|
+
params: {
|
|
46
|
+
stopTicks: 10,
|
|
47
|
+
targetTicks: 40,
|
|
48
|
+
riskReward: '1:4'
|
|
49
|
+
},
|
|
50
|
+
loader: () => require('./hqx-2b')
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// EXPORTS
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get list of available strategies
|
|
60
|
+
* @returns {Array} List of strategy info objects
|
|
61
|
+
*/
|
|
62
|
+
function getAvailableStrategies() {
|
|
63
|
+
return Object.values(STRATEGIES).map(s => ({
|
|
64
|
+
id: s.id,
|
|
65
|
+
name: s.name,
|
|
66
|
+
description: s.description,
|
|
67
|
+
version: s.version,
|
|
68
|
+
backtest: s.backtest,
|
|
69
|
+
params: s.params
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get strategy by ID
|
|
75
|
+
* @param {string} strategyId - Strategy identifier
|
|
76
|
+
* @returns {Object|null} Strategy info or null if not found
|
|
77
|
+
*/
|
|
78
|
+
function getStrategy(strategyId) {
|
|
79
|
+
return STRATEGIES[strategyId] || null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Load strategy module by ID
|
|
84
|
+
* Returns normalized module with M1 as the strategy class
|
|
85
|
+
* @param {string} strategyId - Strategy identifier
|
|
86
|
+
* @returns {Object} Strategy module with M1 class
|
|
87
|
+
*/
|
|
88
|
+
function loadStrategy(strategyId) {
|
|
89
|
+
const strategy = STRATEGIES[strategyId];
|
|
90
|
+
if (!strategy) {
|
|
91
|
+
throw new Error(`Strategy not found: ${strategyId}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let module;
|
|
95
|
+
try {
|
|
96
|
+
module = strategy.loader();
|
|
97
|
+
} catch (e) {
|
|
98
|
+
// If compiled bytecode not found, try to load from private sources (dev mode)
|
|
99
|
+
if (e.code === 'MODULE_NOT_FOUND') {
|
|
100
|
+
try {
|
|
101
|
+
const devPath = path.join(__dirname, '../../../private/strategies',
|
|
102
|
+
strategyId === 'ultra-scalping' ? 'ultra-scalping.js' : 'hqx-2b-liquidity-sweep.js'
|
|
103
|
+
);
|
|
104
|
+
module = require(devPath);
|
|
105
|
+
} catch (devErr) {
|
|
106
|
+
throw new Error(`Failed to load strategy ${strategyId}: ${e.message}`);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Normalize: always return { M1: StrategyClass }
|
|
114
|
+
// Ultra Scalping exports M1, HQX-2B exports M2
|
|
115
|
+
if (module.M1) {
|
|
116
|
+
return module;
|
|
117
|
+
} else if (module.M2) {
|
|
118
|
+
return { M1: module.M2, ...module };
|
|
119
|
+
} else {
|
|
120
|
+
throw new Error(`Strategy ${strategyId} has no valid strategy class (M1 or M2)`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get default strategy ID
|
|
126
|
+
* @returns {string} Default strategy ID
|
|
127
|
+
*/
|
|
128
|
+
function getDefaultStrategy() {
|
|
129
|
+
return 'ultra-scalping';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
STRATEGIES,
|
|
134
|
+
getAvailableStrategies,
|
|
135
|
+
getStrategy,
|
|
136
|
+
loadStrategy,
|
|
137
|
+
getDefaultStrategy
|
|
138
|
+
};
|
package/src/menus/connect.js
CHANGED
|
@@ -8,7 +8,7 @@ const ora = require('ora');
|
|
|
8
8
|
const { connections } = require('../services');
|
|
9
9
|
const { RithmicService } = require('../services/rithmic');
|
|
10
10
|
const { PROPFIRM_CHOICES } = require('../config');
|
|
11
|
-
const { getLogoWidth, centerText, prepareStdin
|
|
11
|
+
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
12
12
|
const { validateUsername, validatePassword } = require('../security');
|
|
13
13
|
const { prompts } = require('../utils');
|
|
14
14
|
|
|
@@ -18,16 +18,16 @@ const { prompts } = require('../utils');
|
|
|
18
18
|
const loginPrompt = async (propfirmName) => {
|
|
19
19
|
prepareStdin();
|
|
20
20
|
console.log();
|
|
21
|
-
console.log(chalk.cyan(`
|
|
21
|
+
console.log(chalk.cyan(`Connecting to ${propfirmName}...`));
|
|
22
22
|
console.log();
|
|
23
23
|
|
|
24
|
-
const username = await prompts.textInput('
|
|
25
|
-
try { validateUsername(input); return undefined; } catch (e) { return e.message
|
|
24
|
+
const username = await prompts.textInput('Username:', '', (input) => {
|
|
25
|
+
try { validateUsername(input); return undefined; } catch (e) { return e.message; }
|
|
26
26
|
});
|
|
27
27
|
if (!username) return null;
|
|
28
28
|
|
|
29
|
-
const pwd = await prompts.passwordInput('
|
|
30
|
-
try { validatePassword(input); return undefined; } catch (e) { return e.message
|
|
29
|
+
const pwd = await prompts.passwordInput('Password:', (input) => {
|
|
30
|
+
try { validatePassword(input); return undefined; } catch (e) { return e.message; }
|
|
31
31
|
});
|
|
32
32
|
if (!pwd) return null;
|
|
33
33
|
|
|
@@ -38,10 +38,6 @@ const loginPrompt = async (propfirmName) => {
|
|
|
38
38
|
* Rithmic menu - Main connection menu
|
|
39
39
|
*/
|
|
40
40
|
const rithmicMenu = async () => {
|
|
41
|
-
// Clear screen and show banner
|
|
42
|
-
clearScreen();
|
|
43
|
-
displayBanner();
|
|
44
|
-
|
|
45
41
|
const propfirms = PROPFIRM_CHOICES;
|
|
46
42
|
const boxWidth = getLogoWidth();
|
|
47
43
|
const innerWidth = boxWidth - 2;
|
|
@@ -50,6 +46,7 @@ const rithmicMenu = async () => {
|
|
|
50
46
|
|
|
51
47
|
const numbered = propfirms.map((pf, i) => ({ num: i + 1, key: pf.value, name: pf.name }));
|
|
52
48
|
|
|
49
|
+
console.log();
|
|
53
50
|
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
54
51
|
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
|
|
55
52
|
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
@@ -74,10 +71,10 @@ const rithmicMenu = async () => {
|
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
|
|
77
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[X]
|
|
74
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[X] Exit', innerWidth)) + chalk.cyan('║'));
|
|
78
75
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
79
76
|
|
|
80
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
77
|
+
const input = await prompts.textInput(chalk.cyan('Select number (or X):'));
|
|
81
78
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
82
79
|
|
|
83
80
|
const action = parseInt(input);
|
|
@@ -87,26 +84,26 @@ const rithmicMenu = async () => {
|
|
|
87
84
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
88
85
|
if (!credentials) return null;
|
|
89
86
|
|
|
90
|
-
const spinner = ora({ text: '
|
|
87
|
+
const spinner = ora({ text: 'Connecting to Rithmic...', color: 'yellow' }).start();
|
|
91
88
|
|
|
92
89
|
try {
|
|
93
90
|
const service = new RithmicService(selectedPropfirm.key);
|
|
94
91
|
const result = await service.login(credentials.username, credentials.password);
|
|
95
92
|
|
|
96
93
|
if (result.success) {
|
|
97
|
-
spinner.text = '
|
|
94
|
+
spinner.text = 'Fetching accounts...';
|
|
98
95
|
const accResult = await service.getTradingAccounts();
|
|
99
96
|
connections.add('rithmic', service, service.propfirm.name);
|
|
100
|
-
spinner.succeed(`
|
|
97
|
+
spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
|
|
101
98
|
await new Promise(r => setTimeout(r, 1500));
|
|
102
99
|
return service;
|
|
103
100
|
} else {
|
|
104
|
-
spinner.fail(
|
|
101
|
+
spinner.fail(result.error || 'Authentication failed');
|
|
105
102
|
await new Promise(r => setTimeout(r, 2000));
|
|
106
103
|
return null;
|
|
107
104
|
}
|
|
108
105
|
} catch (error) {
|
|
109
|
-
spinner.fail(`
|
|
106
|
+
spinner.fail(`Connection error: ${error.message}`);
|
|
110
107
|
await new Promise(r => setTimeout(r, 2000));
|
|
111
108
|
return null;
|
|
112
109
|
}
|