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.
- package/package.json +1 -1
- package/src/app.js +42 -64
- package/src/lib/m/hqx-2b.js +7 -0
- package/src/lib/m/index.js +138 -0
- package/src/lib/m/ultra-scalping.js +7 -0
- package/src/menus/connect.js +14 -17
- package/src/menus/dashboard.js +58 -76
- package/src/pages/accounts.js +38 -49
- package/src/pages/algo/copy-trading.js +546 -178
- package/src/pages/algo/index.js +18 -75
- package/src/pages/algo/one-account.js +322 -57
- package/src/pages/algo/ui.js +15 -15
- package/src/pages/orders.js +19 -22
- package/src/pages/positions.js +19 -22
- package/src/pages/stats/index.js +15 -16
- package/src/pages/user.js +7 -11
- package/src/services/ai-supervision/health.js +35 -47
- package/src/services/index.js +1 -9
- package/src/services/rithmic/accounts.js +8 -6
- package/src/ui/box.js +9 -5
- package/src/ui/index.js +5 -18
- package/src/ui/menu.js +4 -4
- package/src/pages/ai-agents-ui.js +0 -388
- package/src/pages/ai-agents.js +0 -494
- package/src/pages/ai-models.js +0 -389
- package/src/pages/algo/algo-executor.js +0 -307
- package/src/pages/algo/copy-executor.js +0 -331
- package/src/pages/algo/custom-strategy.js +0 -313
- package/src/services/ai-supervision/consensus.js +0 -284
- package/src/services/ai-supervision/context.js +0 -275
- package/src/services/ai-supervision/directive.js +0 -167
- package/src/services/ai-supervision/index.js +0 -359
- package/src/services/ai-supervision/parser.js +0 -278
- package/src/services/ai-supervision/symbols.js +0 -259
- package/src/services/cliproxy/index.js +0 -256
- package/src/services/cliproxy/installer.js +0 -111
- package/src/services/cliproxy/manager.js +0 -387
- package/src/services/llmproxy/index.js +0 -166
- package/src/services/llmproxy/manager.js +0 -411
package/src/pages/orders.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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('
|
|
29
|
+
spinner.fail('No connections found');
|
|
33
30
|
await prompts.waitForEnter();
|
|
34
31
|
return;
|
|
35
32
|
}
|
|
36
|
-
spinner.succeed(`
|
|
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: `
|
|
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
|
|
52
|
+
spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
|
|
56
53
|
} else {
|
|
57
|
-
spinner.warn(`${propfirmName
|
|
54
|
+
spinner.warn(`${propfirmName}: No accounts`);
|
|
58
55
|
}
|
|
59
56
|
} catch (e) {
|
|
60
|
-
spinner.fail(`${propfirmName
|
|
57
|
+
spinner.fail(`${propfirmName}: Failed`);
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
if (allAccounts.length === 0) {
|
|
65
|
-
console.log(chalk.yellow('\n
|
|
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: `
|
|
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
|
|
84
|
+
spinner.succeed(`${accName}: ${result.orders.length} order(s)`);
|
|
88
85
|
} else {
|
|
89
|
-
spinner.succeed(`${accName
|
|
86
|
+
spinner.succeed(`${accName}: No orders`);
|
|
90
87
|
}
|
|
91
88
|
} catch (e) {
|
|
92
|
-
spinner.fail(`${accName
|
|
89
|
+
spinner.fail(`${accName}: Failed to fetch orders`);
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
|
|
96
|
-
spinner = ora({ text: '
|
|
97
|
-
spinner.succeed(`
|
|
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('
|
|
101
|
+
drawBoxRow(chalk.gray(' No orders found'), boxWidth);
|
|
105
102
|
} else {
|
|
106
|
-
const header = ' ' + '
|
|
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('
|
|
133
|
+
if (spinner) spinner.fail('Error: ' + error.message);
|
|
137
134
|
}
|
|
138
135
|
|
|
139
136
|
await prompts.waitForEnter();
|
package/src/pages/positions.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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('
|
|
29
|
+
spinner.fail('No connections found');
|
|
33
30
|
await prompts.waitForEnter();
|
|
34
31
|
return;
|
|
35
32
|
}
|
|
36
|
-
spinner.succeed(`
|
|
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: `
|
|
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
|
|
52
|
+
spinner.succeed(`${propfirmName}: ${result.accounts.length} account(s)`);
|
|
56
53
|
} else {
|
|
57
|
-
spinner.warn(`${propfirmName
|
|
54
|
+
spinner.warn(`${propfirmName}: No accounts`);
|
|
58
55
|
}
|
|
59
56
|
} catch (e) {
|
|
60
|
-
spinner.fail(`${propfirmName
|
|
57
|
+
spinner.fail(`${propfirmName}: Failed`);
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
if (allAccounts.length === 0) {
|
|
65
|
-
console.log(chalk.yellow('\n
|
|
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: `
|
|
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
|
|
84
|
+
spinner.succeed(`${accName}: ${result.positions.length} position(s)`);
|
|
88
85
|
} else {
|
|
89
|
-
spinner.succeed(`${accName
|
|
86
|
+
spinner.succeed(`${accName}: No positions`);
|
|
90
87
|
}
|
|
91
88
|
} catch (e) {
|
|
92
|
-
spinner.fail(`${accName
|
|
89
|
+
spinner.fail(`${accName}: Failed to fetch positions`);
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
|
|
96
|
-
spinner = ora({ text: '
|
|
97
|
-
spinner.succeed(`
|
|
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('
|
|
101
|
+
drawBoxRow(chalk.gray(' No open positions'), boxWidth);
|
|
105
102
|
} else {
|
|
106
|
-
const header = ' ' + '
|
|
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('
|
|
134
|
+
if (spinner) spinner.fail('Error: ' + error.message);
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
await prompts.waitForEnter();
|
package/src/pages/stats/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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: '
|
|
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
|
-
//
|
|
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.
|
|
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
|
|
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: '
|
|
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('
|
|
42
|
+
spinner.succeed('User info loaded');
|
|
47
43
|
|
|
48
44
|
// Step 2: Get account count
|
|
49
|
-
spinner = ora({ text: '
|
|
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(`
|
|
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('
|
|
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('
|
|
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
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
/**
|
package/src/services/index.js
CHANGED
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
* @fileoverview Services module exports
|
|
3
3
|
* @module services
|
|
4
4
|
*
|
|
5
|
-
* Rithmic-only service hub
|
|
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
|
|
249
|
-
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
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 ||
|
|
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:
|
|
25
|
-
|
|
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('
|
|
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
|
|
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
|
|
73
|
-
console.log(chalk.cyan('║') + chalk.
|
|
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.
|
|
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
|
|
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 || '
|
|
100
|
-
console.log(chalk.cyan('║') + chalk.gray(centerText(footerText
|
|
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
|
|