hedgequantx 2.9.19 → 2.9.20

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.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/src/app.js +42 -64
  3. package/src/lib/m/hqx-2b.js +7 -0
  4. package/src/lib/m/index.js +138 -0
  5. package/src/lib/m/ultra-scalping.js +7 -0
  6. package/src/menus/connect.js +14 -17
  7. package/src/menus/dashboard.js +58 -76
  8. package/src/pages/accounts.js +38 -49
  9. package/src/pages/algo/copy-trading.js +546 -178
  10. package/src/pages/algo/index.js +18 -75
  11. package/src/pages/algo/one-account.js +322 -57
  12. package/src/pages/algo/ui.js +15 -15
  13. package/src/pages/orders.js +19 -22
  14. package/src/pages/positions.js +19 -22
  15. package/src/pages/stats/index.js +15 -16
  16. package/src/pages/user.js +7 -11
  17. package/src/services/ai-supervision/health.js +35 -47
  18. package/src/services/index.js +1 -9
  19. package/src/services/rithmic/accounts.js +8 -6
  20. package/src/ui/box.js +9 -5
  21. package/src/ui/index.js +5 -18
  22. package/src/ui/menu.js +4 -4
  23. package/src/pages/ai-agents-ui.js +0 -388
  24. package/src/pages/ai-agents.js +0 -494
  25. package/src/pages/ai-models.js +0 -389
  26. package/src/pages/algo/algo-executor.js +0 -307
  27. package/src/pages/algo/copy-executor.js +0 -331
  28. package/src/pages/algo/custom-strategy.js +0 -313
  29. package/src/services/ai-supervision/consensus.js +0 -284
  30. package/src/services/ai-supervision/context.js +0 -275
  31. package/src/services/ai-supervision/directive.js +0 -167
  32. package/src/services/ai-supervision/index.js +0 -359
  33. package/src/services/ai-supervision/parser.js +0 -278
  34. package/src/services/ai-supervision/symbols.js +0 -259
  35. package/src/services/cliproxy/index.js +0 -256
  36. package/src/services/cliproxy/installer.js +0 -111
  37. package/src/services/cliproxy/manager.js +0 -387
  38. package/src/services/llmproxy/index.js +0 -166
  39. package/src/services/llmproxy/manager.js +0 -411
@@ -7,40 +7,37 @@ const ora = require('ora');
7
7
 
8
8
  const { connections } = require('../services');
9
9
  const { ORDER_STATUS, ORDER_TYPE, ORDER_SIDE } = require('../config');
10
- const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator, displayBanner, clearScreen } = require('../ui');
10
+ const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator } = require('../ui');
11
11
  const { prompts } = require('../utils');
12
12
 
13
13
  /**
14
14
  * Show all orders
15
15
  */
16
16
  const showOrders = async (service) => {
17
- // Clear screen and show banner
18
- clearScreen();
19
- displayBanner();
20
-
21
17
  const boxWidth = getLogoWidth();
22
18
  let spinner;
23
19
 
24
20
  try {
25
- spinner = ora({ text: 'LOADING ORDERS...', color: 'yellow' }).start();
21
+ // Step 1: Get connections
22
+ spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
26
23
 
27
24
  const allConns = connections.count() > 0
28
25
  ? connections.getAll()
29
26
  : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
30
27
 
31
28
  if (allConns.length === 0) {
32
- spinner.fail('NO CONNECTIONS FOUND');
29
+ spinner.fail('No connections found');
33
30
  await prompts.waitForEnter();
34
31
  return;
35
32
  }
36
- spinner.succeed(`FOUND ${allConns.length} CONNECTION(S)`);
33
+ spinner.succeed(`Found ${allConns.length} connection(s)`);
37
34
 
38
35
  // Step 2: Fetch accounts
39
36
  let allAccounts = [];
40
37
 
41
38
  for (const conn of allConns) {
42
39
  const propfirmName = conn.propfirm || conn.type || 'Unknown';
43
- spinner = ora({ text: `FETCHING ACCOUNTS FROM ${propfirmName.toUpperCase()}...`, color: 'yellow' }).start();
40
+ spinner = ora({ text: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
44
41
 
45
42
  try {
46
43
  const result = await conn.service.getTradingAccounts();
@@ -52,17 +49,17 @@ const showOrders = async (service) => {
52
49
  service: conn.service
53
50
  });
54
51
  });
55
- spinner.succeed(`${propfirmName.toUpperCase()}: ${result.accounts.length} ACCOUNT(S)`);
52
+ spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
56
53
  } else {
57
- spinner.warn(`${propfirmName.toUpperCase()}: NO ACCOUNTS`);
54
+ spinner.warn(`${propfirmName}: No accounts`);
58
55
  }
59
56
  } catch (e) {
60
- spinner.fail(`${propfirmName.toUpperCase()}: FAILED`);
57
+ spinner.fail(`${propfirmName}: Failed`);
61
58
  }
62
59
  }
63
60
 
64
61
  if (allAccounts.length === 0) {
65
- console.log(chalk.yellow('\n NO ACCOUNTS FOUND.'));
62
+ console.log(chalk.yellow('\n No accounts found.'));
66
63
  await prompts.waitForEnter();
67
64
  return;
68
65
  }
@@ -72,7 +69,7 @@ const showOrders = async (service) => {
72
69
 
73
70
  for (const account of allAccounts) {
74
71
  const accName = String(account.accountName || account.rithmicAccountId || account.accountId || 'Unknown').substring(0, 20);
75
- spinner = ora({ text: `FETCHING ORDERS FOR ${accName.toUpperCase()}...`, color: 'yellow' }).start();
72
+ spinner = ora({ text: `Fetching orders for ${accName}...`, color: 'yellow' }).start();
76
73
 
77
74
  try {
78
75
  const result = await account.service.getOrders(account.accountId);
@@ -84,26 +81,26 @@ const showOrders = async (service) => {
84
81
  propfirm: account.propfirm
85
82
  });
86
83
  });
87
- spinner.succeed(`${accName.toUpperCase()}: ${result.orders.length} ORDER(S)`);
84
+ spinner.succeed(`${accName}: ${result.orders.length} order(s)`);
88
85
  } else {
89
- spinner.succeed(`${accName.toUpperCase()}: NO ORDERS`);
86
+ spinner.succeed(`${accName}: No orders`);
90
87
  }
91
88
  } catch (e) {
92
- spinner.fail(`${accName.toUpperCase()}: FAILED TO FETCH ORDERS`);
89
+ spinner.fail(`${accName}: Failed to fetch orders`);
93
90
  }
94
91
  }
95
92
 
96
- spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
97
- spinner.succeed(`TOTAL: ${allOrders.length} ORDER(S)`);
93
+ spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
+ spinner.succeed(`Total: ${allOrders.length} order(s)`);
98
95
  console.log();
99
96
 
100
97
  // Display
101
98
  drawBoxHeader('ORDERS', boxWidth);
102
99
 
103
100
  if (allOrders.length === 0) {
104
- drawBoxRow(chalk.gray(' NO ORDERS FOUND'), boxWidth);
101
+ drawBoxRow(chalk.gray(' No orders found'), boxWidth);
105
102
  } else {
106
- 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';
107
104
  drawBoxRow(chalk.white.bold(header), boxWidth);
108
105
  drawBoxSeparator(boxWidth);
109
106
 
@@ -133,7 +130,7 @@ const showOrders = async (service) => {
133
130
  console.log();
134
131
 
135
132
  } catch (error) {
136
- if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
133
+ if (spinner) spinner.fail('Error: ' + error.message);
137
134
  }
138
135
 
139
136
  await prompts.waitForEnter();
@@ -7,40 +7,37 @@ const ora = require('ora');
7
7
 
8
8
  const { connections } = require('../services');
9
9
  const { ORDER_SIDE } = require('../config');
10
- const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator, displayBanner, clearScreen } = require('../ui');
10
+ const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator } = require('../ui');
11
11
  const { prompts } = require('../utils');
12
12
 
13
13
  /**
14
14
  * Show all open positions
15
15
  */
16
16
  const showPositions = async (service) => {
17
- // Clear screen and show banner
18
- clearScreen();
19
- displayBanner();
20
-
21
17
  const boxWidth = getLogoWidth();
22
18
  let spinner;
23
19
 
24
20
  try {
25
- spinner = ora({ text: 'LOADING POSITIONS...', color: 'yellow' }).start();
21
+ // Step 1: Get connections
22
+ spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
26
23
 
27
24
  const allConns = connections.count() > 0
28
25
  ? connections.getAll()
29
26
  : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
30
27
 
31
28
  if (allConns.length === 0) {
32
- spinner.fail('NO CONNECTIONS FOUND');
29
+ spinner.fail('No connections found');
33
30
  await prompts.waitForEnter();
34
31
  return;
35
32
  }
36
- spinner.succeed(`FOUND ${allConns.length} CONNECTION(S)`);
33
+ spinner.succeed(`Found ${allConns.length} connection(s)`);
37
34
 
38
35
  // Step 2: Fetch accounts
39
36
  let allAccounts = [];
40
37
 
41
38
  for (const conn of allConns) {
42
39
  const propfirmName = conn.propfirm || conn.type || 'Unknown';
43
- spinner = ora({ text: `FETCHING ACCOUNTS FROM ${propfirmName.toUpperCase()}...`, color: 'yellow' }).start();
40
+ spinner = ora({ text: `Fetching accounts from ${propfirmName}...`, color: 'yellow' }).start();
44
41
 
45
42
  try {
46
43
  const result = await conn.service.getTradingAccounts();
@@ -52,17 +49,17 @@ const showPositions = async (service) => {
52
49
  service: conn.service
53
50
  });
54
51
  });
55
- spinner.succeed(`${propfirmName.toUpperCase()}: ${result.accounts.length} ACCOUNT(S)`);
52
+ spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
56
53
  } else {
57
- spinner.warn(`${propfirmName.toUpperCase()}: NO ACCOUNTS`);
54
+ spinner.warn(`${propfirmName}: No accounts`);
58
55
  }
59
56
  } catch (e) {
60
- spinner.fail(`${propfirmName.toUpperCase()}: FAILED`);
57
+ spinner.fail(`${propfirmName}: Failed`);
61
58
  }
62
59
  }
63
60
 
64
61
  if (allAccounts.length === 0) {
65
- console.log(chalk.yellow('\n NO ACCOUNTS FOUND.'));
62
+ console.log(chalk.yellow('\n No accounts found.'));
66
63
  await prompts.waitForEnter();
67
64
  return;
68
65
  }
@@ -72,7 +69,7 @@ const showPositions = async (service) => {
72
69
 
73
70
  for (const account of allAccounts) {
74
71
  const accName = String(account.accountName || account.rithmicAccountId || account.accountId || 'Unknown').substring(0, 20);
75
- spinner = ora({ text: `FETCHING POSITIONS FOR ${accName.toUpperCase()}...`, color: 'yellow' }).start();
72
+ spinner = ora({ text: `Fetching positions for ${accName}...`, color: 'yellow' }).start();
76
73
 
77
74
  try {
78
75
  const result = await account.service.getPositions(account.accountId);
@@ -84,26 +81,26 @@ const showPositions = async (service) => {
84
81
  propfirm: account.propfirm
85
82
  });
86
83
  });
87
- spinner.succeed(`${accName.toUpperCase()}: ${result.positions.length} POSITION(S)`);
84
+ spinner.succeed(`${accName}: ${result.positions.length} position(s)`);
88
85
  } else {
89
- spinner.succeed(`${accName.toUpperCase()}: NO POSITIONS`);
86
+ spinner.succeed(`${accName}: No positions`);
90
87
  }
91
88
  } catch (e) {
92
- spinner.fail(`${accName.toUpperCase()}: FAILED TO FETCH POSITIONS`);
89
+ spinner.fail(`${accName}: Failed to fetch positions`);
93
90
  }
94
91
  }
95
92
 
96
- spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
97
- spinner.succeed(`TOTAL: ${allPositions.length} POSITION(S)`);
93
+ spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
+ spinner.succeed(`Total: ${allPositions.length} position(s)`);
98
95
  console.log();
99
96
 
100
97
  // Display
101
98
  drawBoxHeader('OPEN POSITIONS', boxWidth);
102
99
 
103
100
  if (allPositions.length === 0) {
104
- drawBoxRow(chalk.gray(' NO OPEN POSITIONS'), boxWidth);
101
+ drawBoxRow(chalk.gray(' No open positions'), boxWidth);
105
102
  } else {
106
- 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';
107
104
  drawBoxRow(chalk.white.bold(header), boxWidth);
108
105
  drawBoxSeparator(boxWidth);
109
106
 
@@ -134,7 +131,7 @@ const showPositions = async (service) => {
134
131
  console.log();
135
132
 
136
133
  } catch (error) {
137
- if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
134
+ if (spinner) spinner.fail('Error: ' + error.message);
138
135
  }
139
136
 
140
137
  await prompts.waitForEnter();
@@ -11,7 +11,7 @@ const ora = require('ora');
11
11
 
12
12
  const { connections } = require('../../services');
13
13
  const { prompts } = require('../../utils');
14
- const { displayBanner , clearScreen } = require('../../ui');
14
+ const { displayBanner } = require('../../ui');
15
15
  const { aggregateStats, calculateDerivedMetrics, calculateQuantMetrics, calculateHQXScore } = require('./metrics');
16
16
  const { renderOverview, renderPnLMetrics, renderQuantMetrics, renderTradesHistory, renderHQXScore, renderNotice } = require('./display');
17
17
  const { renderEquityCurve } = require('./chart');
@@ -124,7 +124,7 @@ const aggregateAccountData = async (activeAccounts) => {
124
124
  } catch (e) {}
125
125
  }
126
126
 
127
- // Trade history from ORDER_PLANT
127
+ // Trade history (Rithmic doesn't provide this)
128
128
  if (typeof svc.getTradeHistory === 'function') {
129
129
  try {
130
130
  const tradesResult = await svc.getTradeHistory(account.accountId, 30);
@@ -137,9 +137,7 @@ const aggregateAccountData = async (activeAccounts) => {
137
137
  connectionType: connType
138
138
  })));
139
139
  }
140
- } catch (e) {
141
- // Silent - trade history may not be available
142
- }
140
+ } catch (e) {}
143
141
  }
144
142
  } catch (e) {}
145
143
  }
@@ -161,14 +159,10 @@ const aggregateAccountData = async (activeAccounts) => {
161
159
  * Show Stats Page
162
160
  */
163
161
  const showStats = async (service) => {
164
- // Clear screen and show banner
165
- clearScreen();
166
- displayBanner();
167
-
168
162
  let spinner;
169
163
 
170
164
  try {
171
- spinner = ora({ text: 'LOADING STATS...', color: 'yellow' }).start();
165
+ spinner = ora({ text: 'Loading stats...', color: 'yellow' }).start();
172
166
 
173
167
  // Get all connections
174
168
  const allConns = connections.count() > 0
@@ -190,17 +184,22 @@ const showStats = async (service) => {
190
184
  return;
191
185
  }
192
186
 
193
- // Use all accounts (don't filter by status - Rithmic may have different status formats)
194
- const activeAccounts = allAccountsData;
187
+ // Filter active accounts (status === 0)
188
+ const activeAccounts = allAccountsData.filter(acc => acc.status === 0);
189
+
190
+ if (activeAccounts.length === 0) {
191
+ spinner.fail('No active accounts found');
192
+ await prompts.waitForEnter();
193
+ return;
194
+ }
195
195
 
196
196
  // Aggregate account data from APIs
197
197
  const accountData = await aggregateAccountData(activeAccounts);
198
198
 
199
- spinner.stop();
200
-
201
- // Clear and show banner before displaying stats
202
- clearScreen();
199
+ spinner.succeed('Stats loaded');
200
+ console.clear();
203
201
  displayBanner();
202
+ console.log();
204
203
 
205
204
  // Calculate stats from API data
206
205
  const stats = aggregateStats(activeAccounts, accountData.allTrades);
package/src/pages/user.js CHANGED
@@ -6,17 +6,13 @@ const chalk = require('chalk');
6
6
  const ora = require('ora');
7
7
 
8
8
  const { connections } = require('../services');
9
- const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength, padText, displayBanner, clearScreen } = require('../ui');
9
+ const { getLogoWidth, getColWidths, drawBoxHeader, drawBoxFooter, draw2ColHeader, visibleLength, padText } = require('../ui');
10
10
  const { prompts } = require('../utils');
11
11
 
12
12
  /**
13
13
  * Show user info
14
14
  */
15
15
  const showUserInfo = async (service) => {
16
- // Clear screen and show banner
17
- clearScreen();
18
- displayBanner();
19
-
20
16
  const boxWidth = getLogoWidth();
21
17
  const { col1, col2 } = getColWidths(boxWidth);
22
18
  let spinner;
@@ -30,7 +26,7 @@ const showUserInfo = async (service) => {
30
26
 
31
27
  try {
32
28
  // Step 1: Get user info
33
- spinner = ora({ text: 'LOADING USER INFO...', color: 'yellow' }).start();
29
+ spinner = ora({ text: 'Loading user info...', color: 'yellow' }).start();
34
30
 
35
31
  let userInfo = null;
36
32
 
@@ -43,10 +39,10 @@ const showUserInfo = async (service) => {
43
39
  } catch (e) {}
44
40
  }
45
41
 
46
- spinner.succeed('USER INFO LOADED');
42
+ spinner.succeed('User info loaded');
47
43
 
48
44
  // Step 2: Get account count
49
- spinner = ora({ text: 'COUNTING ACCOUNTS...', color: 'yellow' }).start();
45
+ spinner = ora({ text: 'Counting accounts...', color: 'yellow' }).start();
50
46
 
51
47
  let accountCount = 0;
52
48
 
@@ -62,14 +58,14 @@ const showUserInfo = async (service) => {
62
58
  } catch (e) {}
63
59
  }
64
60
 
65
- spinner.succeed(`FOUND ${accountCount} ACCOUNT(S)`);
61
+ spinner.succeed(`Found ${accountCount} account(s)`);
66
62
  console.log();
67
63
 
68
64
  // Display
69
65
  drawBoxHeader('USER INFO', boxWidth);
70
66
 
71
67
  if (!userInfo) {
72
- 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('║'));
73
69
  } else {
74
70
  draw2ColHeader('PROFILE', 'CONNECTIONS', boxWidth);
75
71
 
@@ -94,7 +90,7 @@ const showUserInfo = async (service) => {
94
90
  console.log();
95
91
 
96
92
  } catch (error) {
97
- if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
93
+ if (spinner) spinner.fail('Error: ' + error.message);
98
94
  }
99
95
 
100
96
  await prompts.waitForEnter();
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  const cliproxy = require('../cliproxy');
13
- const https = require('https');
13
+ const { extractJSON } = require('./parser');
14
14
 
15
15
  /** Test prompt to verify agent understands directive format */
16
16
  const TEST_PROMPT = `You are being tested. Respond ONLY with this exact JSON, nothing else:
@@ -161,6 +161,7 @@ const testAgentConnection = async (agent) => {
161
161
 
162
162
  /**
163
163
  * Validate that response matches expected JSON format
164
+ * Uses robust extractJSON from parser.js to handle MiniMax <think> tags and other edge cases
164
165
  * @param {string} content - Response content from agent
165
166
  * @returns {Object} { valid, error }
166
167
  */
@@ -169,53 +170,40 @@ const validateResponseFormat = (content) => {
169
170
  return { valid: false, error: 'Empty response' };
170
171
  }
171
172
 
172
- try {
173
- // Try to extract JSON from response
174
- let json;
175
-
176
- // Direct parse
177
- try {
178
- json = JSON.parse(content.trim());
179
- } catch (e) {
180
- // Try to find JSON in response
181
- const match = content.match(/\{[\s\S]*\}/);
182
- if (match) {
183
- json = JSON.parse(match[0]);
184
- } else {
185
- return { valid: false, error: 'No JSON in response' };
186
- }
187
- }
188
-
189
- // Check required fields
190
- if (!json.decision) {
191
- return { valid: false, error: 'Missing "decision" field' };
192
- }
193
-
194
- if (json.confidence === undefined) {
195
- return { valid: false, error: 'Missing "confidence" field' };
196
- }
197
-
198
- if (!json.reason) {
199
- return { valid: false, error: 'Missing "reason" field' };
200
- }
201
-
202
- // Validate decision value
203
- const validDecisions = ['approve', 'reject', 'modify'];
204
- if (!validDecisions.includes(json.decision)) {
205
- return { valid: false, error: `Invalid decision: ${json.decision}` };
206
- }
207
-
208
- // Validate confidence is number 0-100
209
- const conf = Number(json.confidence);
210
- if (isNaN(conf) || conf < 0 || conf > 100) {
211
- return { valid: false, error: `Invalid confidence: ${json.confidence}` };
212
- }
213
-
214
- return { valid: true, error: null };
215
-
216
- } catch (error) {
217
- return { valid: false, error: `Parse error: ${error.message}` };
173
+ // Use robust JSON extraction from parser.js
174
+ // Handles: direct JSON, markdown code blocks, JSON with extra text (MiniMax <think> tags)
175
+ const json = extractJSON(content);
176
+
177
+ if (!json) {
178
+ return { valid: false, error: 'No valid JSON found in response' };
179
+ }
180
+
181
+ // Check required fields
182
+ if (!json.decision) {
183
+ return { valid: false, error: 'Missing "decision" field' };
184
+ }
185
+
186
+ if (json.confidence === undefined) {
187
+ return { valid: false, error: 'Missing "confidence" field' };
218
188
  }
189
+
190
+ if (!json.reason) {
191
+ return { valid: false, error: 'Missing "reason" field' };
192
+ }
193
+
194
+ // Validate decision value
195
+ const validDecisions = ['approve', 'reject', 'modify'];
196
+ if (!validDecisions.includes(json.decision)) {
197
+ return { valid: false, error: `Invalid decision: ${json.decision}` };
198
+ }
199
+
200
+ // Validate confidence is number 0-100
201
+ const conf = Number(json.confidence);
202
+ if (isNaN(conf) || conf < 0 || conf > 100) {
203
+ return { valid: false, error: `Invalid confidence: ${json.confidence}` };
204
+ }
205
+
206
+ return { valid: true, error: null };
219
207
  };
220
208
 
221
209
  /**
@@ -2,14 +2,12 @@
2
2
  * @fileoverview Services module exports
3
3
  * @module services
4
4
  *
5
- * Rithmic-only service hub + AI Supervision + Dual Proxy Support
5
+ * Rithmic-only service hub
6
6
  */
7
7
 
8
8
  const { RithmicService } = require('./rithmic/index');
9
9
  const { HQXServerService } = require('./hqx-server/index');
10
10
  const { storage, connections } = require('./session');
11
- const aiSupervision = require('./ai-supervision');
12
- const llmproxy = require('./llmproxy');
13
11
 
14
12
  module.exports = {
15
13
  // Platform Service (Rithmic only)
@@ -21,10 +19,4 @@ module.exports = {
21
19
  // Session Management
22
20
  storage,
23
21
  connections,
24
-
25
- // AI Supervision
26
- aiSupervision,
27
-
28
- // LLM API Proxy (for API key providers via LiteLLM)
29
- llmproxy,
30
22
  };
@@ -245,16 +245,18 @@ const getTradingAccounts = async (service) => {
245
245
  profitAndLoss: profitAndLoss,
246
246
  openPnL: openPnL,
247
247
  todayPnL: closedPnL,
248
- // Status from Rithmic RMS API (field 154003 - ACCOUNT_STATUS)
249
- status: rmsInfo.status || null,
250
- // Algorithm/Type from Rithmic RMS API (field 150142 - ALGORITHM)
251
- algorithm: rmsInfo.algorithm || null,
252
- // Additional RMS data from API
248
+ // Status/Type: Rithmic API doesn't provide user-friendly values
249
+ // "admin only" and "Max Loss" are RMS internal values, not account status
250
+ // Set to null to show "--" in UI (per RULES.md - no fake data)
251
+ status: null,
252
+ type: null,
253
+ // Keep RMS data for reference
254
+ rmsStatus: rmsInfo.status || null,
255
+ rmsAlgorithm: rmsInfo.algorithm || null,
253
256
  lossLimit: rmsInfo.lossLimit || null,
254
257
  minAccountBalance: rmsInfo.minAccountBalance || null,
255
258
  buyLimit: rmsInfo.buyLimit || null,
256
259
  sellLimit: rmsInfo.sellLimit || null,
257
- currency: rmsInfo.currency || acc.accountCurrency || null,
258
260
  platform: 'Rithmic',
259
261
  propfirm: service.propfirm.name,
260
262
  };
package/src/ui/box.js CHANGED
@@ -10,19 +10,23 @@ 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)
14
13
  * Adapts to terminal width for mobile devices
15
14
  */
16
15
  const getLogoWidth = () => {
17
- const termWidth = process.stdout.columns || 100;
16
+ const termWidth = process.stdout.columns || 80;
18
17
 
19
18
  // Mobile: use terminal width
20
19
  if (termWidth < 60) {
21
20
  return Math.max(termWidth - 2, 40);
22
21
  }
23
22
 
24
- // Desktop: fixed width 98 to match banner
25
- return Math.min(98, termWidth - 2);
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);
26
30
  };
27
31
 
28
32
  /**
@@ -93,7 +97,7 @@ const drawBoxSeparator = (width) => {
93
97
  const printLogo = () => {
94
98
  const logoText = figlet.textSync('HEDGEQUANTX', { font: 'ANSI Shadow' });
95
99
  console.log(chalk.cyan(logoText));
96
- console.log(chalk.gray.italic(' PROP FUTURES ALGO TRADING CLI'));
100
+ console.log(chalk.gray.italic(' Prop Futures Algo Trading CLI'));
97
101
  console.log();
98
102
  };
99
103
 
package/src/ui/index.js CHANGED
@@ -26,7 +26,8 @@ const {
26
26
  const { createBoxMenu } = require('./menu');
27
27
 
28
28
  /**
29
- * Display HQX Banner - ALWAYS closed with bottom border
29
+ * Display HQX Banner (without closing border)
30
+ * Note: console.clear() is handled by app.js banner() to avoid terminal bugs
30
31
  */
31
32
  const displayBanner = () => {
32
33
  const termWidth = process.stdout.columns || 100;
@@ -69,20 +70,8 @@ const displayBanner = () => {
69
70
  }
70
71
 
71
72
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
72
- const tagline = isMobile ? `HQX V${version}` : `PROP FUTURES ALGO TRADING V${version}`;
73
- console.log(chalk.cyan('║') + chalk.yellow(centerText(tagline, innerWidth)) + chalk.cyan('║'));
74
-
75
- // ALWAYS close the banner
76
- console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
77
- };
78
-
79
- /**
80
- * Clear screen without using alternate screen buffer
81
- * Uses ANSI escape codes directly to avoid terminal state issues
82
- */
83
- const clearScreen = () => {
84
- // ESC[H = home, ESC[2J = clear screen, ESC[3J = clear scrollback
85
- process.stdout.write('\x1B[H\x1B[2J\x1B[3J');
73
+ const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
74
+ console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
86
75
  };
87
76
 
88
77
  /**
@@ -132,7 +121,5 @@ module.exports = {
132
121
  // Stdin
133
122
  prepareStdin,
134
123
  // Banner
135
- displayBanner,
136
- // Screen
137
- clearScreen
124
+ displayBanner
138
125
  };
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.yellow(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.toUpperCase() + (item.disabled ? ' (COMING SOON)' : '');
84
+ const label = item.label + (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.toUpperCase(), innerWidth)) + chalk.cyan('║'));
99
+ const footerText = options.footerText || 'Use ↑↓ arrows to navigate, Enter to select';
100
+ console.log(chalk.cyan('║') + chalk.gray(centerText(footerText, innerWidth)) + chalk.cyan('║'));
101
101
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
102
102
  };
103
103