hedgequantx 2.7.35 → 2.7.37
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 +1 -1
- package/src/menus/connect.js +12 -12
- package/src/pages/accounts.js +5 -5
- package/src/pages/ai-agents-ui.js +33 -30
- package/src/pages/ai-agents.js +38 -7
- package/src/pages/algo/ui.js +14 -14
- package/src/pages/orders.js +17 -17
- package/src/pages/positions.js +17 -17
- package/src/pages/stats/index.js +1 -2
- package/src/pages/user.js +6 -6
- package/src/ui/box.js +5 -9
- package/src/ui/index.js +5 -8
- package/src/ui/menu.js +4 -4
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -132,7 +132,7 @@ const banner = async (withLoading = false) => {
|
|
|
132
132
|
|
|
133
133
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
134
134
|
|
|
135
|
-
const tagline = isMobile ? `HQX
|
|
135
|
+
const tagline = isMobile ? `HQX V${version}` : `PROP FUTURES ALGO TRADING V${version}`;
|
|
136
136
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
137
137
|
|
|
138
138
|
// Show loading message if requested
|
package/src/menus/connect.js
CHANGED
|
@@ -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.toUpperCase()}...`));
|
|
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.toUpperCase(); }
|
|
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.toUpperCase(); }
|
|
31
31
|
});
|
|
32
32
|
if (!pwd) return null;
|
|
33
33
|
|
|
@@ -71,10 +71,10 @@ const rithmicMenu = async () => {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
|
|
74
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[X]
|
|
74
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[X] EXIT', innerWidth)) + chalk.cyan('║'));
|
|
75
75
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
76
76
|
|
|
77
|
-
const input = await prompts.textInput(chalk.cyan('
|
|
77
|
+
const input = await prompts.textInput(chalk.cyan('SELECT NUMBER (OR X):'));
|
|
78
78
|
if (!input || input.toLowerCase() === 'x') return null;
|
|
79
79
|
|
|
80
80
|
const action = parseInt(input);
|
|
@@ -84,26 +84,26 @@ const rithmicMenu = async () => {
|
|
|
84
84
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
85
85
|
if (!credentials) return null;
|
|
86
86
|
|
|
87
|
-
const spinner = ora({ text: '
|
|
87
|
+
const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
|
|
88
88
|
|
|
89
89
|
try {
|
|
90
90
|
const service = new RithmicService(selectedPropfirm.key);
|
|
91
91
|
const result = await service.login(credentials.username, credentials.password);
|
|
92
92
|
|
|
93
93
|
if (result.success) {
|
|
94
|
-
spinner.text = '
|
|
94
|
+
spinner.text = 'FETCHING ACCOUNTS...';
|
|
95
95
|
const accResult = await service.getTradingAccounts();
|
|
96
96
|
connections.add('rithmic', service, service.propfirm.name);
|
|
97
|
-
spinner.succeed(`
|
|
97
|
+
spinner.succeed(`CONNECTED TO ${service.propfirm.name.toUpperCase()} (${accResult.accounts?.length || 0} ACCOUNTS)`);
|
|
98
98
|
await new Promise(r => setTimeout(r, 1500));
|
|
99
99
|
return service;
|
|
100
100
|
} else {
|
|
101
|
-
spinner.fail(result.error || '
|
|
101
|
+
spinner.fail((result.error || 'AUTHENTICATION FAILED').toUpperCase());
|
|
102
102
|
await new Promise(r => setTimeout(r, 2000));
|
|
103
103
|
return null;
|
|
104
104
|
}
|
|
105
105
|
} catch (error) {
|
|
106
|
-
spinner.fail(`
|
|
106
|
+
spinner.fail(`CONNECTION ERROR: ${error.message.toUpperCase()}`);
|
|
107
107
|
await new Promise(r => setTimeout(r, 2000));
|
|
108
108
|
return null;
|
|
109
109
|
}
|
package/src/pages/accounts.js
CHANGED
|
@@ -29,12 +29,12 @@ const showAccounts = async (service) => {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
// Single spinner for loading (appears below the dashboard header)
|
|
32
|
-
spinner = ora({ text: '
|
|
32
|
+
spinner = ora({ text: 'LOADING ACCOUNTS...', color: 'yellow' }).start();
|
|
33
33
|
|
|
34
34
|
const allConns = connections.count() > 0 ? connections.getAll() : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
35
35
|
|
|
36
36
|
if (allConns.length === 0) {
|
|
37
|
-
spinner.fail('
|
|
37
|
+
spinner.fail('NO CONNECTIONS FOUND');
|
|
38
38
|
await prompts.waitForEnter();
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
@@ -60,7 +60,7 @@ const showAccounts = async (service) => {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (allAccounts.length === 0) {
|
|
63
|
-
spinner.fail('
|
|
63
|
+
spinner.fail('NO ACCOUNTS FOUND');
|
|
64
64
|
await prompts.waitForEnter();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -78,7 +78,7 @@ const showAccounts = async (service) => {
|
|
|
78
78
|
} catch (e) {}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
spinner.succeed('
|
|
81
|
+
spinner.succeed('ACCOUNTS LOADED');
|
|
82
82
|
console.log();
|
|
83
83
|
|
|
84
84
|
// Display accounts
|
|
@@ -162,7 +162,7 @@ const showAccounts = async (service) => {
|
|
|
162
162
|
console.log();
|
|
163
163
|
|
|
164
164
|
} catch (error) {
|
|
165
|
-
if (spinner) spinner.fail('
|
|
165
|
+
if (spinner) spinner.fail('ERROR LOADING ACCOUNTS: ' + error.message.toUpperCase());
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
await prompts.waitForEnter();
|
|
@@ -8,26 +8,27 @@ const chalk = require('chalk');
|
|
|
8
8
|
const { centerText, visibleLength } = require('../ui');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Draw a 2-column row
|
|
11
|
+
* Draw a 2-column row with perfect alignment
|
|
12
12
|
* @param {string} leftText - Left column text
|
|
13
13
|
* @param {string} rightText - Right column text
|
|
14
14
|
* @param {number} W - Inner width
|
|
15
|
+
* @param {number} padding - Left padding for each column (default 3)
|
|
15
16
|
*/
|
|
16
|
-
const draw2ColRow = (leftText, rightText, W) => {
|
|
17
|
-
const
|
|
18
|
-
const col2Width = W - col1Width;
|
|
17
|
+
const draw2ColRow = (leftText, rightText, W, padding = 3) => {
|
|
18
|
+
const colWidth = Math.floor(W / 2);
|
|
19
19
|
const leftLen = visibleLength(leftText);
|
|
20
|
-
const leftPad = col1Width - leftLen;
|
|
21
|
-
const leftPadL = Math.floor(leftPad / 2);
|
|
22
20
|
const rightLen = visibleLength(rightText || '');
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
|
|
22
|
+
// Left column: padding + text + fill to colWidth
|
|
23
|
+
const leftFill = colWidth - padding - leftLen;
|
|
24
|
+
const leftCol = ' '.repeat(padding) + leftText + ' '.repeat(Math.max(0, leftFill));
|
|
25
|
+
|
|
26
|
+
// Right column: padding + text + fill to remaining width
|
|
27
|
+
const rightColWidth = W - colWidth;
|
|
28
|
+
const rightFill = rightColWidth - padding - rightLen;
|
|
29
|
+
const rightCol = ' '.repeat(padding) + (rightText || '') + ' '.repeat(Math.max(0, rightFill));
|
|
30
|
+
|
|
31
|
+
console.log(chalk.cyan('║') + leftCol + rightCol + chalk.cyan('║'));
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -39,6 +40,7 @@ const draw2ColRow = (leftText, rightText, W) => {
|
|
|
39
40
|
* @param {number} W - Inner width
|
|
40
41
|
*/
|
|
41
42
|
const draw2ColTable = (title, titleColor, items, backText, W) => {
|
|
43
|
+
// New rectangle (banner is always closed)
|
|
42
44
|
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
43
45
|
console.log(chalk.cyan('║') + titleColor(centerText(title, W)) + chalk.cyan('║'));
|
|
44
46
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
@@ -65,6 +67,7 @@ const draw2ColTable = (title, titleColor, items, backText, W) => {
|
|
|
65
67
|
const drawProvidersTable = (providers, config, boxWidth, cliproxyUrl = null) => {
|
|
66
68
|
const W = boxWidth - 2;
|
|
67
69
|
|
|
70
|
+
// New rectangle (banner is always closed)
|
|
68
71
|
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
69
72
|
console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('AI AGENTS CONFIGURATION', W)) + chalk.cyan('║'));
|
|
70
73
|
|
|
@@ -79,7 +82,7 @@ const drawProvidersTable = (providers, config, boxWidth, cliproxyUrl = null) =>
|
|
|
79
82
|
|
|
80
83
|
const items = providers.map((p, i) => {
|
|
81
84
|
const status = config.providers[p.id]?.active ? chalk.green(' ●') : '';
|
|
82
|
-
return chalk.cyan(`[${i + 1}]`) + ' ' + chalk[p.color](p.name) + status;
|
|
85
|
+
return chalk.cyan(`[${i + 1}]`) + ' ' + chalk[p.color](p.name.toUpperCase()) + status;
|
|
83
86
|
});
|
|
84
87
|
|
|
85
88
|
const rows = Math.ceil(items.length / 2);
|
|
@@ -90,9 +93,9 @@ const drawProvidersTable = (providers, config, boxWidth, cliproxyUrl = null) =>
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
93
|
-
console.log(chalk.cyan('║') + chalk.gray(centerText('[S]
|
|
96
|
+
console.log(chalk.cyan('║') + chalk.gray(centerText('[S] CLIPROXY STATUS', W)) + chalk.cyan('║'));
|
|
94
97
|
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
95
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[B]
|
|
98
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK TO MENU', W)) + chalk.cyan('║'));
|
|
96
99
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
97
100
|
};
|
|
98
101
|
|
|
@@ -104,8 +107,8 @@ const drawProvidersTable = (providers, config, boxWidth, cliproxyUrl = null) =>
|
|
|
104
107
|
*/
|
|
105
108
|
const drawModelsTable = (provider, models, boxWidth) => {
|
|
106
109
|
const W = boxWidth - 2;
|
|
107
|
-
const items = models.map((m, i) => chalk.cyan(`[${i + 1}]`) + ' ' + chalk.white(m.name));
|
|
108
|
-
draw2ColTable(`${provider.name.toUpperCase()} - MODELS`, chalk[provider.color].bold, items, '[B]
|
|
110
|
+
const items = models.map((m, i) => chalk.cyan(`[${i + 1}]`) + ' ' + chalk.white(m.name.toUpperCase()));
|
|
111
|
+
draw2ColTable(`${provider.name.toUpperCase()} - MODELS`, chalk[provider.color].bold, items, '[B] BACK', W);
|
|
109
112
|
};
|
|
110
113
|
|
|
111
114
|
/**
|
|
@@ -120,7 +123,7 @@ const drawProviderWindow = (provider, config, boxWidth) => {
|
|
|
120
123
|
const col2Width = W - col1Width;
|
|
121
124
|
const providerConfig = config.providers[provider.id] || {};
|
|
122
125
|
|
|
123
|
-
//
|
|
126
|
+
// New rectangle (banner is always closed)
|
|
124
127
|
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
125
128
|
console.log(chalk.cyan('║') + chalk[provider.color].bold(centerText(provider.name.toUpperCase(), W)) + chalk.cyan('║'));
|
|
126
129
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
@@ -129,10 +132,10 @@ const drawProviderWindow = (provider, config, boxWidth) => {
|
|
|
129
132
|
console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
|
|
130
133
|
|
|
131
134
|
// Options in 2 columns
|
|
132
|
-
const opt1Title = '[1]
|
|
133
|
-
const opt1Desc = '
|
|
134
|
-
const opt2Title = '[2]
|
|
135
|
-
const opt2Desc = '
|
|
135
|
+
const opt1Title = '[1] CONNECT VIA PAID PLAN';
|
|
136
|
+
const opt1Desc = 'USES CLIPROXY - NO API KEY NEEDED';
|
|
137
|
+
const opt2Title = '[2] CONNECT VIA API KEY';
|
|
138
|
+
const opt2Desc = 'ENTER YOUR OWN API KEY';
|
|
136
139
|
|
|
137
140
|
// Row 1: Titles
|
|
138
141
|
const left1 = chalk.green(opt1Title);
|
|
@@ -180,11 +183,11 @@ const drawProviderWindow = (provider, config, boxWidth) => {
|
|
|
180
183
|
|
|
181
184
|
let statusText = '';
|
|
182
185
|
if (providerConfig.active) {
|
|
183
|
-
const connType = providerConfig.connectionType === 'cliproxy' ? '
|
|
184
|
-
const modelName = providerConfig.modelName || 'N/A';
|
|
185
|
-
statusText = chalk.green('● ACTIVE') + chalk.gray('
|
|
186
|
+
const connType = providerConfig.connectionType === 'cliproxy' ? 'CLIPROXY' : 'API KEY';
|
|
187
|
+
const modelName = (providerConfig.modelName || 'N/A').toUpperCase();
|
|
188
|
+
statusText = chalk.green('● ACTIVE') + chalk.gray(' MODEL: ') + chalk.yellow(modelName) + chalk.gray(' VIA ') + chalk.cyan(connType);
|
|
186
189
|
} else if (providerConfig.apiKey || providerConfig.connectionType) {
|
|
187
|
-
statusText = chalk.yellow('● CONFIGURED') + chalk.gray(' (
|
|
190
|
+
statusText = chalk.yellow('● CONFIGURED') + chalk.gray(' (NOT ACTIVE)');
|
|
188
191
|
} else {
|
|
189
192
|
statusText = chalk.gray('○ NOT CONFIGURED');
|
|
190
193
|
}
|
|
@@ -193,12 +196,12 @@ const drawProviderWindow = (provider, config, boxWidth) => {
|
|
|
193
196
|
// Disconnect option if active
|
|
194
197
|
if (providerConfig.active) {
|
|
195
198
|
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
196
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[D]
|
|
199
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[D] DISCONNECT', W)) + chalk.cyan('║'));
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
// Back
|
|
200
203
|
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
201
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[B]
|
|
204
|
+
console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
|
|
202
205
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
203
206
|
};
|
|
204
207
|
|
package/src/pages/ai-agents.js
CHANGED
|
@@ -17,10 +17,10 @@ const { fetchModelsFromApi } = require('./ai-models');
|
|
|
17
17
|
const { drawProvidersTable, drawModelsTable, drawProviderWindow } = require('./ai-agents-ui');
|
|
18
18
|
const cliproxy = require('../services/cliproxy');
|
|
19
19
|
|
|
20
|
-
/** Clear screen and show banner */
|
|
20
|
+
/** Clear screen and show banner (always closed) */
|
|
21
21
|
const clearWithBanner = () => {
|
|
22
22
|
console.clear();
|
|
23
|
-
displayBanner();
|
|
23
|
+
displayBanner(); // Banner always closed
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
// Config file path
|
|
@@ -241,15 +241,46 @@ const handleCliProxyConnection = async (provider, config, boxWidth) => {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
// Restart CLIProxyAPI to load new tokens
|
|
244
|
-
|
|
244
|
+
const restartSpinner = ora({ text: 'RESTARTING CLIPROXYAPI...', color: 'yellow' }).start();
|
|
245
245
|
await cliproxy.stop();
|
|
246
|
-
await new Promise(r => setTimeout(r,
|
|
246
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
247
247
|
await cliproxy.start();
|
|
248
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
249
248
|
|
|
250
|
-
//
|
|
249
|
+
// Wait for CLIProxyAPI to be fully ready (check health)
|
|
250
|
+
restartSpinner.text = 'WAITING FOR CLIPROXYAPI TO BE READY...';
|
|
251
|
+
let ready = false;
|
|
252
|
+
for (let i = 0; i < 10; i++) {
|
|
253
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
254
|
+
const status = await cliproxy.isRunning();
|
|
255
|
+
if (status.running) {
|
|
256
|
+
// Try a simple fetch to verify it's responding
|
|
257
|
+
const test = await cliproxy.fetchModels();
|
|
258
|
+
if (test.success) {
|
|
259
|
+
ready = true;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
restartSpinner.text = `WAITING FOR CLIPROXYAPI (${i + 1}/10)...`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!ready) {
|
|
267
|
+
restartSpinner.warn('CLIPROXYAPI SLOW TO START - CONTINUING...');
|
|
268
|
+
} else {
|
|
269
|
+
restartSpinner.succeed('CLIPROXYAPI READY');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Fetch models (with retry for provider-specific)
|
|
251
273
|
const modelSpinner = ora({ text: 'FETCHING AVAILABLE MODELS...', color: 'yellow' }).start();
|
|
252
|
-
|
|
274
|
+
|
|
275
|
+
let modelsResult = { success: false, models: [] };
|
|
276
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
277
|
+
modelsResult = await cliproxy.fetchProviderModels(provider.id);
|
|
278
|
+
if (modelsResult.success && modelsResult.models.length > 0) break;
|
|
279
|
+
if (attempt < 5) {
|
|
280
|
+
modelSpinner.text = `FETCHING MODELS (ATTEMPT ${attempt + 1}/5)...`;
|
|
281
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
253
284
|
|
|
254
285
|
if (modelsResult.success && modelsResult.models.length > 0) {
|
|
255
286
|
modelSpinner.succeed(`FOUND ${modelsResult.models.length} MODELS`);
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -127,9 +127,9 @@ class AlgoUI {
|
|
|
127
127
|
|
|
128
128
|
// Separator + title
|
|
129
129
|
this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
130
|
-
this._line(chalk.cyan(BOX.V) + chalk.white(center(`
|
|
130
|
+
this._line(chalk.cyan(BOX.V) + chalk.white(center(`PROP FUTURES ALGO TRADING V${version}`, W)) + chalk.cyan(BOX.V));
|
|
131
131
|
this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
132
|
-
this._line(chalk.cyan(BOX.V) + chalk.yellow(center(this.config.subtitle || 'HQX
|
|
132
|
+
this._line(chalk.cyan(BOX.V) + chalk.yellow(center((this.config.subtitle || 'HQX ALGO TRADING').toUpperCase(), W)) + chalk.cyan(BOX.V));
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
_drawStats(stats) {
|
|
@@ -293,7 +293,7 @@ class AlgoUI {
|
|
|
293
293
|
const visible = logs.slice(-maxLogs).reverse();
|
|
294
294
|
|
|
295
295
|
if (visible.length === 0) {
|
|
296
|
-
this._line(chalk.cyan(BOX.V) + chalk.gray(fitToWidth('
|
|
296
|
+
this._line(chalk.cyan(BOX.V) + chalk.gray(fitToWidth(' AWAITING MARKET SIGNALS...', W)) + chalk.cyan(BOX.V));
|
|
297
297
|
for (let i = 0; i < maxLogs - 1; i++) {
|
|
298
298
|
this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
|
|
299
299
|
}
|
|
@@ -355,11 +355,11 @@ const checkMarketStatus = () => {
|
|
|
355
355
|
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
356
356
|
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
357
357
|
|
|
358
|
-
if (ctDay === 6) return { isOpen: false, message: '
|
|
359
|
-
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: '
|
|
360
|
-
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: '
|
|
361
|
-
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: '
|
|
362
|
-
return { isOpen: true, message: '
|
|
358
|
+
if (ctDay === 6) return { isOpen: false, message: 'MARKET CLOSED (SATURDAY)' };
|
|
359
|
+
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'MARKET OPENS SUNDAY 5:00 PM CT' };
|
|
360
|
+
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'MARKET CLOSED (FRIDAY AFTER 4PM CT)' };
|
|
361
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'DAILY MAINTENANCE' };
|
|
362
|
+
return { isOpen: true, message: 'MARKET OPEN' };
|
|
363
363
|
};
|
|
364
364
|
|
|
365
365
|
/**
|
|
@@ -387,9 +387,9 @@ const renderSessionSummary = (stats, stopReason) => {
|
|
|
387
387
|
|
|
388
388
|
// Separator + title
|
|
389
389
|
console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
390
|
-
console.log(chalk.cyan(BOX.V) + chalk.white(center(`
|
|
390
|
+
console.log(chalk.cyan(BOX.V) + chalk.white(center(`PROP FUTURES ALGO TRADING V${version}`, W)) + chalk.cyan(BOX.V));
|
|
391
391
|
console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
392
|
-
console.log(chalk.cyan(BOX.V) + chalk.yellow(center('
|
|
392
|
+
console.log(chalk.cyan(BOX.V) + chalk.yellow(center('SESSION SUMMARY', W)) + chalk.cyan(BOX.V));
|
|
393
393
|
|
|
394
394
|
// Grid separators
|
|
395
395
|
const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
|
|
@@ -410,18 +410,18 @@ const renderSessionSummary = (stats, stopReason) => {
|
|
|
410
410
|
// Row 1: Stop Reason | Duration
|
|
411
411
|
const duration = stats.duration || '--';
|
|
412
412
|
const reasonColor = stopReason === 'target' ? chalk.green : stopReason === 'risk' ? chalk.red : chalk.yellow;
|
|
413
|
-
row('
|
|
413
|
+
row('STOP REASON', (stopReason || 'MANUAL').toUpperCase(), reasonColor, 'DURATION', duration, chalk.white);
|
|
414
414
|
|
|
415
415
|
console.log(chalk.cyan(GM));
|
|
416
416
|
|
|
417
417
|
// Row 2: Trades | Win Rate
|
|
418
418
|
const winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) + '%' : '0%';
|
|
419
|
-
row('
|
|
419
|
+
row('TRADES', String(stats.trades || 0), chalk.white, 'WIN RATE', winRate, stats.wins >= stats.losses ? chalk.green : chalk.red);
|
|
420
420
|
|
|
421
421
|
console.log(chalk.cyan(GM));
|
|
422
422
|
|
|
423
423
|
// Row 3: Wins | Losses
|
|
424
|
-
row('
|
|
424
|
+
row('WINS', String(stats.wins || 0), chalk.green, 'LOSSES', String(stats.losses || 0), chalk.red);
|
|
425
425
|
|
|
426
426
|
console.log(chalk.cyan(GM));
|
|
427
427
|
|
|
@@ -430,7 +430,7 @@ const renderSessionSummary = (stats, stopReason) => {
|
|
|
430
430
|
const pnlStr = `${pnl >= 0 ? '+' : ''}$${Math.abs(pnl).toFixed(2)}`;
|
|
431
431
|
const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
|
|
432
432
|
const targetStr = `$${(stats.target || 0).toFixed(2)}`;
|
|
433
|
-
row('P&L', pnlStr, pnlColor, '
|
|
433
|
+
row('P&L', pnlStr, pnlColor, 'TARGET', targetStr, chalk.cyan);
|
|
434
434
|
|
|
435
435
|
// Bottom border
|
|
436
436
|
console.log(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
|
package/src/pages/orders.js
CHANGED
|
@@ -19,25 +19,25 @@ const showOrders = async (service) => {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
// Step 1: Get connections
|
|
22
|
-
spinner = ora({ text: '
|
|
22
|
+
spinner = ora({ text: 'LOADING CONNECTIONS...', color: 'yellow' }).start();
|
|
23
23
|
|
|
24
24
|
const allConns = connections.count() > 0
|
|
25
25
|
? connections.getAll()
|
|
26
26
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
27
27
|
|
|
28
28
|
if (allConns.length === 0) {
|
|
29
|
-
spinner.fail('
|
|
29
|
+
spinner.fail('NO CONNECTIONS FOUND');
|
|
30
30
|
await prompts.waitForEnter();
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
spinner.succeed(`
|
|
33
|
+
spinner.succeed(`FOUND ${allConns.length} CONNECTION(S)`);
|
|
34
34
|
|
|
35
35
|
// Step 2: Fetch accounts
|
|
36
36
|
let allAccounts = [];
|
|
37
37
|
|
|
38
38
|
for (const conn of allConns) {
|
|
39
39
|
const propfirmName = conn.propfirm || conn.type || 'Unknown';
|
|
40
|
-
spinner = ora({ text: `
|
|
40
|
+
spinner = ora({ text: `FETCHING ACCOUNTS FROM ${propfirmName.toUpperCase()}...`, color: 'yellow' }).start();
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
43
|
const result = await conn.service.getTradingAccounts();
|
|
@@ -49,17 +49,17 @@ const showOrders = async (service) => {
|
|
|
49
49
|
service: conn.service
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
|
-
spinner.succeed(`${propfirmName}: ${result.accounts.length}
|
|
52
|
+
spinner.succeed(`${propfirmName.toUpperCase()}: ${result.accounts.length} ACCOUNT(S)`);
|
|
53
53
|
} else {
|
|
54
|
-
spinner.warn(`${propfirmName}:
|
|
54
|
+
spinner.warn(`${propfirmName.toUpperCase()}: NO ACCOUNTS`);
|
|
55
55
|
}
|
|
56
56
|
} catch (e) {
|
|
57
|
-
spinner.fail(`${propfirmName}:
|
|
57
|
+
spinner.fail(`${propfirmName.toUpperCase()}: FAILED`);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (allAccounts.length === 0) {
|
|
62
|
-
console.log(chalk.yellow('\n
|
|
62
|
+
console.log(chalk.yellow('\n NO ACCOUNTS FOUND.'));
|
|
63
63
|
await prompts.waitForEnter();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -69,7 +69,7 @@ const showOrders = async (service) => {
|
|
|
69
69
|
|
|
70
70
|
for (const account of allAccounts) {
|
|
71
71
|
const accName = String(account.accountName || account.rithmicAccountId || account.accountId || 'Unknown').substring(0, 20);
|
|
72
|
-
spinner = ora({ text: `
|
|
72
|
+
spinner = ora({ text: `FETCHING ORDERS FOR ${accName.toUpperCase()}...`, color: 'yellow' }).start();
|
|
73
73
|
|
|
74
74
|
try {
|
|
75
75
|
const result = await account.service.getOrders(account.accountId);
|
|
@@ -81,26 +81,26 @@ const showOrders = async (service) => {
|
|
|
81
81
|
propfirm: account.propfirm
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
|
-
spinner.succeed(`${accName}: ${result.orders.length}
|
|
84
|
+
spinner.succeed(`${accName.toUpperCase()}: ${result.orders.length} ORDER(S)`);
|
|
85
85
|
} else {
|
|
86
|
-
spinner.succeed(`${accName}:
|
|
86
|
+
spinner.succeed(`${accName.toUpperCase()}: NO ORDERS`);
|
|
87
87
|
}
|
|
88
88
|
} catch (e) {
|
|
89
|
-
spinner.fail(`${accName}:
|
|
89
|
+
spinner.fail(`${accName.toUpperCase()}: FAILED TO FETCH ORDERS`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
spinner = ora({ text: '
|
|
94
|
-
spinner.succeed(`
|
|
93
|
+
spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
|
|
94
|
+
spinner.succeed(`TOTAL: ${allOrders.length} ORDER(S)`);
|
|
95
95
|
console.log();
|
|
96
96
|
|
|
97
97
|
// Display
|
|
98
98
|
drawBoxHeader('ORDERS', boxWidth);
|
|
99
99
|
|
|
100
100
|
if (allOrders.length === 0) {
|
|
101
|
-
drawBoxRow(chalk.gray('
|
|
101
|
+
drawBoxRow(chalk.gray(' NO ORDERS FOUND'), boxWidth);
|
|
102
102
|
} else {
|
|
103
|
-
const header = ' ' + '
|
|
103
|
+
const header = ' ' + 'SYMBOL'.padEnd(12) + 'SIDE'.padEnd(6) + 'TYPE'.padEnd(8) + 'QTY'.padEnd(6) + 'PRICE'.padEnd(10) + 'STATUS'.padEnd(12) + 'ACCOUNT';
|
|
104
104
|
drawBoxRow(chalk.white.bold(header), boxWidth);
|
|
105
105
|
drawBoxSeparator(boxWidth);
|
|
106
106
|
|
|
@@ -130,7 +130,7 @@ const showOrders = async (service) => {
|
|
|
130
130
|
console.log();
|
|
131
131
|
|
|
132
132
|
} catch (error) {
|
|
133
|
-
if (spinner) spinner.fail('
|
|
133
|
+
if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
await prompts.waitForEnter();
|
package/src/pages/positions.js
CHANGED
|
@@ -19,25 +19,25 @@ const showPositions = async (service) => {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
// Step 1: Get connections
|
|
22
|
-
spinner = ora({ text: '
|
|
22
|
+
spinner = ora({ text: 'LOADING CONNECTIONS...', color: 'yellow' }).start();
|
|
23
23
|
|
|
24
24
|
const allConns = connections.count() > 0
|
|
25
25
|
? connections.getAll()
|
|
26
26
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
27
27
|
|
|
28
28
|
if (allConns.length === 0) {
|
|
29
|
-
spinner.fail('
|
|
29
|
+
spinner.fail('NO CONNECTIONS FOUND');
|
|
30
30
|
await prompts.waitForEnter();
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
spinner.succeed(`
|
|
33
|
+
spinner.succeed(`FOUND ${allConns.length} CONNECTION(S)`);
|
|
34
34
|
|
|
35
35
|
// Step 2: Fetch accounts
|
|
36
36
|
let allAccounts = [];
|
|
37
37
|
|
|
38
38
|
for (const conn of allConns) {
|
|
39
39
|
const propfirmName = conn.propfirm || conn.type || 'Unknown';
|
|
40
|
-
spinner = ora({ text: `
|
|
40
|
+
spinner = ora({ text: `FETCHING ACCOUNTS FROM ${propfirmName.toUpperCase()}...`, color: 'yellow' }).start();
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
43
|
const result = await conn.service.getTradingAccounts();
|
|
@@ -49,17 +49,17 @@ const showPositions = async (service) => {
|
|
|
49
49
|
service: conn.service
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
|
-
spinner.succeed(`${propfirmName}: ${result.accounts.length}
|
|
52
|
+
spinner.succeed(`${propfirmName.toUpperCase()}: ${result.accounts.length} ACCOUNT(S)`);
|
|
53
53
|
} else {
|
|
54
|
-
spinner.warn(`${propfirmName}:
|
|
54
|
+
spinner.warn(`${propfirmName.toUpperCase()}: NO ACCOUNTS`);
|
|
55
55
|
}
|
|
56
56
|
} catch (e) {
|
|
57
|
-
spinner.fail(`${propfirmName}:
|
|
57
|
+
spinner.fail(`${propfirmName.toUpperCase()}: FAILED`);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (allAccounts.length === 0) {
|
|
62
|
-
console.log(chalk.yellow('\n
|
|
62
|
+
console.log(chalk.yellow('\n NO ACCOUNTS FOUND.'));
|
|
63
63
|
await prompts.waitForEnter();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -69,7 +69,7 @@ const showPositions = async (service) => {
|
|
|
69
69
|
|
|
70
70
|
for (const account of allAccounts) {
|
|
71
71
|
const accName = String(account.accountName || account.rithmicAccountId || account.accountId || 'Unknown').substring(0, 20);
|
|
72
|
-
spinner = ora({ text: `
|
|
72
|
+
spinner = ora({ text: `FETCHING POSITIONS FOR ${accName.toUpperCase()}...`, color: 'yellow' }).start();
|
|
73
73
|
|
|
74
74
|
try {
|
|
75
75
|
const result = await account.service.getPositions(account.accountId);
|
|
@@ -81,26 +81,26 @@ const showPositions = async (service) => {
|
|
|
81
81
|
propfirm: account.propfirm
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
|
-
spinner.succeed(`${accName}: ${result.positions.length}
|
|
84
|
+
spinner.succeed(`${accName.toUpperCase()}: ${result.positions.length} POSITION(S)`);
|
|
85
85
|
} else {
|
|
86
|
-
spinner.succeed(`${accName}:
|
|
86
|
+
spinner.succeed(`${accName.toUpperCase()}: NO POSITIONS`);
|
|
87
87
|
}
|
|
88
88
|
} catch (e) {
|
|
89
|
-
spinner.fail(`${accName}:
|
|
89
|
+
spinner.fail(`${accName.toUpperCase()}: FAILED TO FETCH POSITIONS`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
spinner = ora({ text: '
|
|
94
|
-
spinner.succeed(`
|
|
93
|
+
spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
|
|
94
|
+
spinner.succeed(`TOTAL: ${allPositions.length} POSITION(S)`);
|
|
95
95
|
console.log();
|
|
96
96
|
|
|
97
97
|
// Display
|
|
98
98
|
drawBoxHeader('OPEN POSITIONS', boxWidth);
|
|
99
99
|
|
|
100
100
|
if (allPositions.length === 0) {
|
|
101
|
-
drawBoxRow(chalk.gray('
|
|
101
|
+
drawBoxRow(chalk.gray(' NO OPEN POSITIONS'), boxWidth);
|
|
102
102
|
} else {
|
|
103
|
-
const header = ' ' + '
|
|
103
|
+
const header = ' ' + 'SYMBOL'.padEnd(15) + 'SIDE'.padEnd(8) + 'SIZE'.padEnd(8) + 'ENTRY'.padEnd(12) + 'P&L'.padEnd(12) + 'ACCOUNT';
|
|
104
104
|
drawBoxRow(chalk.white.bold(header), boxWidth);
|
|
105
105
|
drawBoxSeparator(boxWidth);
|
|
106
106
|
|
|
@@ -131,7 +131,7 @@ const showPositions = async (service) => {
|
|
|
131
131
|
console.log();
|
|
132
132
|
|
|
133
133
|
} catch (error) {
|
|
134
|
-
if (spinner) spinner.fail('
|
|
134
|
+
if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
await prompts.waitForEnter();
|
package/src/pages/stats/index.js
CHANGED
|
@@ -198,8 +198,7 @@ const showStats = async (service) => {
|
|
|
198
198
|
|
|
199
199
|
spinner.succeed('Stats loaded');
|
|
200
200
|
console.clear();
|
|
201
|
-
displayBanner();
|
|
202
|
-
console.log();
|
|
201
|
+
displayBanner(); // Banner always closed
|
|
203
202
|
|
|
204
203
|
// Calculate stats from API data
|
|
205
204
|
const stats = aggregateStats(activeAccounts, accountData.allTrades);
|
package/src/pages/user.js
CHANGED
|
@@ -26,7 +26,7 @@ const showUserInfo = async (service) => {
|
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
28
|
// Step 1: Get user info
|
|
29
|
-
spinner = ora({ text: '
|
|
29
|
+
spinner = ora({ text: 'LOADING USER INFO...', color: 'yellow' }).start();
|
|
30
30
|
|
|
31
31
|
let userInfo = null;
|
|
32
32
|
|
|
@@ -39,10 +39,10 @@ const showUserInfo = async (service) => {
|
|
|
39
39
|
} catch (e) {}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
spinner.succeed('
|
|
42
|
+
spinner.succeed('USER INFO LOADED');
|
|
43
43
|
|
|
44
44
|
// Step 2: Get account count
|
|
45
|
-
spinner = ora({ text: '
|
|
45
|
+
spinner = ora({ text: 'COUNTING ACCOUNTS...', color: 'yellow' }).start();
|
|
46
46
|
|
|
47
47
|
let accountCount = 0;
|
|
48
48
|
|
|
@@ -58,14 +58,14 @@ const showUserInfo = async (service) => {
|
|
|
58
58
|
} catch (e) {}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
spinner.succeed(`
|
|
61
|
+
spinner.succeed(`FOUND ${accountCount} ACCOUNT(S)`);
|
|
62
62
|
console.log();
|
|
63
63
|
|
|
64
64
|
// Display
|
|
65
65
|
drawBoxHeader('USER INFO', boxWidth);
|
|
66
66
|
|
|
67
67
|
if (!userInfo) {
|
|
68
|
-
console.log(chalk.cyan('║') + padText(chalk.gray('
|
|
68
|
+
console.log(chalk.cyan('║') + padText(chalk.gray(' NO USER INFO AVAILABLE'), boxWidth - 2) + chalk.cyan('║'));
|
|
69
69
|
} else {
|
|
70
70
|
draw2ColHeader('PROFILE', 'CONNECTIONS', boxWidth);
|
|
71
71
|
|
|
@@ -90,7 +90,7 @@ const showUserInfo = async (service) => {
|
|
|
90
90
|
console.log();
|
|
91
91
|
|
|
92
92
|
} catch (error) {
|
|
93
|
-
if (spinner) spinner.fail('
|
|
93
|
+
if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
await prompts.waitForEnter();
|
package/src/ui/box.js
CHANGED
|
@@ -10,23 +10,19 @@ let logoWidth = null;
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Get logo width for consistent box sizing
|
|
13
|
+
* Fixed width of 98 to match HQX banner (logo 88 + X 8 + padding 2)
|
|
13
14
|
* Adapts to terminal width for mobile devices
|
|
14
15
|
*/
|
|
15
16
|
const getLogoWidth = () => {
|
|
16
|
-
const termWidth = process.stdout.columns ||
|
|
17
|
+
const termWidth = process.stdout.columns || 100;
|
|
17
18
|
|
|
18
19
|
// Mobile: use terminal width
|
|
19
20
|
if (termWidth < 60) {
|
|
20
21
|
return Math.max(termWidth - 2, 40);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
// Desktop:
|
|
24
|
-
|
|
25
|
-
const logoText = figlet.textSync('HEDGEQUANTX', { font: 'ANSI Shadow' });
|
|
26
|
-
const lines = logoText.split('\n').filter(line => line.trim().length > 0);
|
|
27
|
-
logoWidth = Math.max(...lines.map(line => line.length)) + 4;
|
|
28
|
-
}
|
|
29
|
-
return Math.min(logoWidth, termWidth - 2);
|
|
24
|
+
// Desktop: fixed width 98 to match banner
|
|
25
|
+
return Math.min(98, termWidth - 2);
|
|
30
26
|
};
|
|
31
27
|
|
|
32
28
|
/**
|
|
@@ -97,7 +93,7 @@ const drawBoxSeparator = (width) => {
|
|
|
97
93
|
const printLogo = () => {
|
|
98
94
|
const logoText = figlet.textSync('HEDGEQUANTX', { font: 'ANSI Shadow' });
|
|
99
95
|
console.log(chalk.cyan(logoText));
|
|
100
|
-
console.log(chalk.gray.italic('
|
|
96
|
+
console.log(chalk.gray.italic(' PROP FUTURES ALGO TRADING CLI'));
|
|
101
97
|
console.log();
|
|
102
98
|
};
|
|
103
99
|
|
package/src/ui/index.js
CHANGED
|
@@ -26,10 +26,9 @@ const {
|
|
|
26
26
|
const { createBoxMenu } = require('./menu');
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Display HQX Banner
|
|
30
|
-
* @param {boolean} closed - If true, add bottom border
|
|
29
|
+
* Display HQX Banner - ALWAYS closed with bottom border
|
|
31
30
|
*/
|
|
32
|
-
const displayBanner = (
|
|
31
|
+
const displayBanner = () => {
|
|
33
32
|
const termWidth = process.stdout.columns || 100;
|
|
34
33
|
const isMobile = termWidth < 60;
|
|
35
34
|
const boxWidth = isMobile ? Math.max(termWidth - 2, 40) : Math.max(getLogoWidth(), 98);
|
|
@@ -70,13 +69,11 @@ const displayBanner = (closed = false) => {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
73
|
-
const tagline = isMobile ? `HQX
|
|
72
|
+
const tagline = isMobile ? `HQX V${version}` : `PROP FUTURES ALGO TRADING V${version}`;
|
|
74
73
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
75
74
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
79
|
-
}
|
|
75
|
+
// ALWAYS close the banner
|
|
76
|
+
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
80
77
|
};
|
|
81
78
|
|
|
82
79
|
/**
|
package/src/ui/menu.js
CHANGED
|
@@ -48,7 +48,7 @@ const createBoxMenu = async (title, items, options = {}) => {
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
51
|
-
console.log(chalk.cyan('║') + chalk.white(centerText(`
|
|
51
|
+
console.log(chalk.cyan('║') + chalk.white(centerText(`PROP FUTURES ALGO TRADING V${version}`, innerWidth)) + chalk.cyan('║'));
|
|
52
52
|
|
|
53
53
|
// Stats bar if provided
|
|
54
54
|
if (options.statsLine) {
|
|
@@ -81,7 +81,7 @@ const createBoxMenu = async (title, items, options = {}) => {
|
|
|
81
81
|
const isSelected = index === selectedIndex;
|
|
82
82
|
const prefix = isSelected ? chalk.white('▸ ') : ' ';
|
|
83
83
|
const color = item.disabled ? chalk.gray : (item.color || chalk.cyan);
|
|
84
|
-
const label = item.label + (item.disabled ? ' (
|
|
84
|
+
const label = item.label.toUpperCase() + (item.disabled ? ' (COMING SOON)' : '');
|
|
85
85
|
const text = prefix + color(label);
|
|
86
86
|
const visLen = text.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
87
87
|
const padding = innerWidth - visLen;
|
|
@@ -96,8 +96,8 @@ const createBoxMenu = async (title, items, options = {}) => {
|
|
|
96
96
|
|
|
97
97
|
// Footer
|
|
98
98
|
console.log(chalk.cyan('╠' + '─'.repeat(innerWidth) + '╣'));
|
|
99
|
-
const footerText = options.footerText || '
|
|
100
|
-
console.log(chalk.cyan('║') + chalk.gray(centerText(footerText, innerWidth)) + chalk.cyan('║'));
|
|
99
|
+
const footerText = options.footerText || 'USE ↑↓ ARROWS TO NAVIGATE, ENTER TO SELECT';
|
|
100
|
+
console.log(chalk.cyan('║') + chalk.gray(centerText(footerText.toUpperCase(), innerWidth)) + chalk.cyan('║'));
|
|
101
101
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
102
102
|
};
|
|
103
103
|
|