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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.7.35",
3
+ "version": "2.7.37",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
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 v${version}` : `Prop Futures Algo Trading v${version}`;
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
@@ -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(`Connecting to ${propfirmName}...`));
21
+ console.log(chalk.cyan(`CONNECTING TO ${propfirmName.toUpperCase()}...`));
22
22
  console.log();
23
23
 
24
- const username = await prompts.textInput('Username:', '', (input) => {
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('Password:', (input) => {
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] Exit', innerWidth)) + chalk.cyan('║'));
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('Select number (or X):'));
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: 'Connecting to Rithmic...', color: 'yellow' }).start();
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 = 'Fetching accounts...';
94
+ spinner.text = 'FETCHING ACCOUNTS...';
95
95
  const accResult = await service.getTradingAccounts();
96
96
  connections.add('rithmic', service, service.propfirm.name);
97
- spinner.succeed(`Connected to ${service.propfirm.name} (${accResult.accounts?.length || 0} accounts)`);
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 || 'Authentication failed');
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(`Connection error: ${error.message}`);
106
+ spinner.fail(`CONNECTION ERROR: ${error.message.toUpperCase()}`);
107
107
  await new Promise(r => setTimeout(r, 2000));
108
108
  return null;
109
109
  }
@@ -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: 'Loading accounts...', color: 'yellow' }).start();
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('No connections found');
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('No accounts found');
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('Accounts loaded');
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('Error loading accounts: ' + error.message);
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 col1Width = Math.floor(W / 2);
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
- const rightPad = col2Width - rightLen;
24
- const rightPadL = Math.floor(rightPad / 2);
25
- console.log(
26
- chalk.cyan('') +
27
- ' '.repeat(leftPadL) + leftText + ' '.repeat(leftPad - leftPadL) +
28
- ' '.repeat(rightPadL) + (rightText || '') + ' '.repeat(rightPad - rightPadL) +
29
- chalk.cyan('║')
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] CLIProxy Status', W)) + chalk.cyan('║'));
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] Back to Menu', W)) + chalk.cyan('║'));
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] Back', W);
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
- // Header
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] Connect via Paid Plan';
133
- const opt1Desc = 'Uses CLIProxy - No API key needed';
134
- const opt2Title = '[2] Connect via API Key';
135
- const opt2Desc = 'Enter your own API key';
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' ? 'CLIProxy' : 'API Key';
184
- const modelName = providerConfig.modelName || 'N/A';
185
- statusText = chalk.green('● ACTIVE') + chalk.gray(' Model: ') + chalk.yellow(modelName) + chalk.gray(' via ') + chalk.cyan(connType);
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(' (not active)');
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] Disconnect', W)) + chalk.cyan('║'));
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] Back', W)) + chalk.cyan('║'));
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
 
@@ -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
- console.log(chalk.gray(' RESTARTING CLIPROXYAPI...'));
244
+ const restartSpinner = ora({ text: 'RESTARTING CLIPROXYAPI...', color: 'yellow' }).start();
245
245
  await cliproxy.stop();
246
- await new Promise(r => setTimeout(r, 1000));
246
+ await new Promise(r => setTimeout(r, 1500));
247
247
  await cliproxy.start();
248
- await new Promise(r => setTimeout(r, 2000));
249
248
 
250
- // Try to fetch models after auth
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
- const modelsResult = await cliproxy.fetchProviderModels(provider.id);
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`);
@@ -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(`Prop Futures Algo Trading v${version}`, W)) + chalk.cyan(BOX.V));
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 Algo Trading', W)) + chalk.cyan(BOX.V));
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(' Awaiting market signals...', W)) + chalk.cyan(BOX.V));
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: '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' };
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(`Prop Futures Algo Trading v${version}`, W)) + chalk.cyan(BOX.V));
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('Session Summary', W)) + chalk.cyan(BOX.V));
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('Stop Reason', (stopReason || 'manual').toUpperCase(), reasonColor, 'Duration', duration, chalk.white);
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('Trades', String(stats.trades || 0), chalk.white, 'Win Rate', winRate, stats.wins >= stats.losses ? chalk.green : chalk.red);
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('Wins', String(stats.wins || 0), chalk.green, 'Losses', String(stats.losses || 0), chalk.red);
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, 'Target', targetStr, chalk.cyan);
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));
@@ -19,25 +19,25 @@ const showOrders = async (service) => {
19
19
 
20
20
  try {
21
21
  // Step 1: Get connections
22
- spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
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('No connections found');
29
+ spinner.fail('NO CONNECTIONS FOUND');
30
30
  await prompts.waitForEnter();
31
31
  return;
32
32
  }
33
- spinner.succeed(`Found ${allConns.length} connection(s)`);
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: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
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} account(s)`);
52
+ spinner.succeed(`${propfirmName.toUpperCase()}: ${result.accounts.length} ACCOUNT(S)`);
53
53
  } else {
54
- spinner.warn(`${propfirmName}: No accounts`);
54
+ spinner.warn(`${propfirmName.toUpperCase()}: NO ACCOUNTS`);
55
55
  }
56
56
  } catch (e) {
57
- spinner.fail(`${propfirmName}: Failed`);
57
+ spinner.fail(`${propfirmName.toUpperCase()}: FAILED`);
58
58
  }
59
59
  }
60
60
 
61
61
  if (allAccounts.length === 0) {
62
- console.log(chalk.yellow('\n No accounts found.'));
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: `Fetching orders for ${accName}...`, color: 'yellow' }).start();
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} order(s)`);
84
+ spinner.succeed(`${accName.toUpperCase()}: ${result.orders.length} ORDER(S)`);
85
85
  } else {
86
- spinner.succeed(`${accName}: No orders`);
86
+ spinner.succeed(`${accName.toUpperCase()}: NO ORDERS`);
87
87
  }
88
88
  } catch (e) {
89
- spinner.fail(`${accName}: Failed to fetch orders`);
89
+ spinner.fail(`${accName.toUpperCase()}: FAILED TO FETCH ORDERS`);
90
90
  }
91
91
  }
92
92
 
93
- spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
- spinner.succeed(`Total: ${allOrders.length} order(s)`);
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(' No orders found'), boxWidth);
101
+ drawBoxRow(chalk.gray(' NO ORDERS FOUND'), boxWidth);
102
102
  } else {
103
- const header = ' ' + 'Symbol'.padEnd(12) + 'Side'.padEnd(6) + 'Type'.padEnd(8) + 'Qty'.padEnd(6) + 'Price'.padEnd(10) + 'Status'.padEnd(12) + 'Account';
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('Error: ' + error.message);
133
+ if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
134
134
  }
135
135
 
136
136
  await prompts.waitForEnter();
@@ -19,25 +19,25 @@ const showPositions = async (service) => {
19
19
 
20
20
  try {
21
21
  // Step 1: Get connections
22
- spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
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('No connections found');
29
+ spinner.fail('NO CONNECTIONS FOUND');
30
30
  await prompts.waitForEnter();
31
31
  return;
32
32
  }
33
- spinner.succeed(`Found ${allConns.length} connection(s)`);
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: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
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} account(s)`);
52
+ spinner.succeed(`${propfirmName.toUpperCase()}: ${result.accounts.length} ACCOUNT(S)`);
53
53
  } else {
54
- spinner.warn(`${propfirmName}: No accounts`);
54
+ spinner.warn(`${propfirmName.toUpperCase()}: NO ACCOUNTS`);
55
55
  }
56
56
  } catch (e) {
57
- spinner.fail(`${propfirmName}: Failed`);
57
+ spinner.fail(`${propfirmName.toUpperCase()}: FAILED`);
58
58
  }
59
59
  }
60
60
 
61
61
  if (allAccounts.length === 0) {
62
- console.log(chalk.yellow('\n No accounts found.'));
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: `Fetching positions for ${accName}...`, color: 'yellow' }).start();
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} position(s)`);
84
+ spinner.succeed(`${accName.toUpperCase()}: ${result.positions.length} POSITION(S)`);
85
85
  } else {
86
- spinner.succeed(`${accName}: No positions`);
86
+ spinner.succeed(`${accName.toUpperCase()}: NO POSITIONS`);
87
87
  }
88
88
  } catch (e) {
89
- spinner.fail(`${accName}: Failed to fetch positions`);
89
+ spinner.fail(`${accName.toUpperCase()}: FAILED TO FETCH POSITIONS`);
90
90
  }
91
91
  }
92
92
 
93
- spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
- spinner.succeed(`Total: ${allPositions.length} position(s)`);
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(' No open positions'), boxWidth);
101
+ drawBoxRow(chalk.gray(' NO OPEN POSITIONS'), boxWidth);
102
102
  } else {
103
- const header = ' ' + 'Symbol'.padEnd(15) + 'Side'.padEnd(8) + 'Size'.padEnd(8) + 'Entry'.padEnd(12) + 'P&L'.padEnd(12) + 'Account';
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('Error: ' + error.message);
134
+ if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
135
135
  }
136
136
 
137
137
  await prompts.waitForEnter();
@@ -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: 'Loading user info...', color: 'yellow' }).start();
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('User info loaded');
42
+ spinner.succeed('USER INFO LOADED');
43
43
 
44
44
  // Step 2: Get account count
45
- spinner = ora({ text: 'Counting accounts...', color: 'yellow' }).start();
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(`Found ${accountCount} account(s)`);
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(' No user info available'), boxWidth - 2) + chalk.cyan('║'));
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('Error: ' + error.message);
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 || 80;
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: use logo width
24
- if (!logoWidth) {
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(' Prop Futures Algo Trading CLI'));
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 (without closing border by default)
30
- * @param {boolean} closed - If true, add bottom border
29
+ * Display HQX Banner - ALWAYS closed with bottom border
31
30
  */
32
- const displayBanner = (closed = false) => {
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 v${version}` : `Prop Futures Algo Trading v${version}`;
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
- // Close the box if requested
77
- if (closed) {
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(`Prop Futures Algo Trading v${version}`, innerWidth)) + chalk.cyan('║'));
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 ? ' (Coming Soon)' : '');
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 || 'Use ↑↓ arrows to navigate, Enter to select';
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