hedgequantx 2.9.20 → 2.9.22

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 (36) hide show
  1. package/package.json +1 -1
  2. package/src/app.js +64 -42
  3. package/src/menus/connect.js +17 -14
  4. package/src/menus/dashboard.js +76 -58
  5. package/src/pages/accounts.js +49 -38
  6. package/src/pages/ai-agents-ui.js +388 -0
  7. package/src/pages/ai-agents.js +494 -0
  8. package/src/pages/ai-models.js +389 -0
  9. package/src/pages/algo/algo-executor.js +307 -0
  10. package/src/pages/algo/copy-executor.js +331 -0
  11. package/src/pages/algo/copy-trading.js +178 -546
  12. package/src/pages/algo/custom-strategy.js +313 -0
  13. package/src/pages/algo/index.js +75 -18
  14. package/src/pages/algo/one-account.js +57 -322
  15. package/src/pages/algo/ui.js +15 -15
  16. package/src/pages/orders.js +22 -19
  17. package/src/pages/positions.js +22 -19
  18. package/src/pages/stats/index.js +16 -15
  19. package/src/pages/user.js +11 -7
  20. package/src/services/ai-supervision/consensus.js +284 -0
  21. package/src/services/ai-supervision/context.js +275 -0
  22. package/src/services/ai-supervision/directive.js +167 -0
  23. package/src/services/ai-supervision/health.js +47 -35
  24. package/src/services/ai-supervision/index.js +359 -0
  25. package/src/services/ai-supervision/parser.js +278 -0
  26. package/src/services/ai-supervision/symbols.js +259 -0
  27. package/src/services/cliproxy/index.js +256 -0
  28. package/src/services/cliproxy/installer.js +111 -0
  29. package/src/services/cliproxy/manager.js +387 -0
  30. package/src/services/index.js +9 -1
  31. package/src/services/llmproxy/index.js +166 -0
  32. package/src/services/llmproxy/manager.js +411 -0
  33. package/src/services/rithmic/accounts.js +6 -8
  34. package/src/ui/box.js +5 -9
  35. package/src/ui/index.js +18 -5
  36. package/src/ui/menu.js +4 -4
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Symbol Data for AI Supervision
3
+ *
4
+ * Contains detailed information about tradeable symbols
5
+ * including tick sizes, sessions, correlations, and characteristics.
6
+ */
7
+
8
+ const SYMBOLS = {
9
+ NQ: {
10
+ id: 'NQ',
11
+ name: 'E-mini Nasdaq 100',
12
+ exchange: 'CME',
13
+ tickSize: 0.25,
14
+ tickValue: 5.00,
15
+ pointValue: 20.00,
16
+ margin: 15000,
17
+ characteristics: {
18
+ volatility: 'high',
19
+ sector: 'technology',
20
+ behavior: 'momentum-driven, gap-prone',
21
+ avgRange: '150-300 points'
22
+ },
23
+ correlations: {
24
+ positive: ['ES', 'YM'],
25
+ negative: ['VIX'],
26
+ related: ['QQQ', 'AAPL', 'MSFT', 'NVDA']
27
+ },
28
+ sessions: {
29
+ most_active: ['us_open', 'us_close'],
30
+ avoid: ['asia_lunch', 'low_volume']
31
+ }
32
+ },
33
+
34
+ ES: {
35
+ id: 'ES',
36
+ name: 'E-mini S&P 500',
37
+ exchange: 'CME',
38
+ tickSize: 0.25,
39
+ tickValue: 12.50,
40
+ pointValue: 50.00,
41
+ margin: 12000,
42
+ characteristics: {
43
+ volatility: 'medium',
44
+ sector: 'broad_market',
45
+ behavior: 'reference index, institutional flow',
46
+ avgRange: '30-60 points'
47
+ },
48
+ correlations: {
49
+ positive: ['NQ', 'YM', 'RTY'],
50
+ negative: ['VIX', 'ZB'],
51
+ related: ['SPY', 'SPX']
52
+ },
53
+ sessions: {
54
+ most_active: ['us_open', 'us_close', 'london_us_overlap'],
55
+ avoid: ['asia_session']
56
+ }
57
+ },
58
+
59
+ YM: {
60
+ id: 'YM',
61
+ name: 'E-mini Dow',
62
+ exchange: 'CME',
63
+ tickSize: 1.00,
64
+ tickValue: 5.00,
65
+ pointValue: 5.00,
66
+ margin: 10000,
67
+ characteristics: {
68
+ volatility: 'medium-low',
69
+ sector: 'value_stocks',
70
+ behavior: 'slower than ES/NQ, less spiky',
71
+ avgRange: '200-400 points'
72
+ },
73
+ correlations: {
74
+ positive: ['ES', 'NQ'],
75
+ negative: ['VIX'],
76
+ related: ['DIA', 'DJIA']
77
+ },
78
+ sessions: {
79
+ most_active: ['us_open', 'us_close'],
80
+ avoid: ['overnight']
81
+ }
82
+ },
83
+
84
+ RTY: {
85
+ id: 'RTY',
86
+ name: 'E-mini Russell 2000',
87
+ exchange: 'CME',
88
+ tickSize: 0.10,
89
+ tickValue: 5.00,
90
+ pointValue: 50.00,
91
+ margin: 8000,
92
+ characteristics: {
93
+ volatility: 'high',
94
+ sector: 'small_caps',
95
+ behavior: 'more volatile than ES, liquidity gaps',
96
+ avgRange: '20-40 points'
97
+ },
98
+ correlations: {
99
+ positive: ['ES', 'NQ'],
100
+ negative: ['VIX'],
101
+ related: ['IWM', 'RUT']
102
+ },
103
+ sessions: {
104
+ most_active: ['us_open'],
105
+ avoid: ['overnight', 'low_volume']
106
+ }
107
+ },
108
+
109
+ GC: {
110
+ id: 'GC',
111
+ name: 'Gold Futures',
112
+ exchange: 'COMEX',
113
+ tickSize: 0.10,
114
+ tickValue: 10.00,
115
+ pointValue: 100.00,
116
+ margin: 11000,
117
+ characteristics: {
118
+ volatility: 'medium',
119
+ sector: 'precious_metals',
120
+ behavior: 'safe haven, inverse USD, central bank sensitive',
121
+ avgRange: '15-30 points'
122
+ },
123
+ correlations: {
124
+ positive: ['SI', 'EURUSD'],
125
+ negative: ['DXY', 'US10Y'],
126
+ related: ['GLD', 'XAUUSD']
127
+ },
128
+ sessions: {
129
+ most_active: ['london', 'us_open', 'asia_open'],
130
+ avoid: ['us_afternoon']
131
+ }
132
+ },
133
+
134
+ SI: {
135
+ id: 'SI',
136
+ name: 'Silver Futures',
137
+ exchange: 'COMEX',
138
+ tickSize: 0.005,
139
+ tickValue: 25.00,
140
+ pointValue: 5000.00,
141
+ margin: 9000,
142
+ characteristics: {
143
+ volatility: 'high',
144
+ sector: 'precious_metals',
145
+ behavior: 'follows gold with more volatility, industrial demand',
146
+ avgRange: '0.30-0.60 points'
147
+ },
148
+ correlations: {
149
+ positive: ['GC'],
150
+ negative: ['DXY'],
151
+ related: ['SLV', 'XAGUSD']
152
+ },
153
+ sessions: {
154
+ most_active: ['london', 'us_open'],
155
+ avoid: ['asia_lunch']
156
+ }
157
+ },
158
+
159
+ CL: {
160
+ id: 'CL',
161
+ name: 'Crude Oil Futures',
162
+ exchange: 'NYMEX',
163
+ tickSize: 0.01,
164
+ tickValue: 10.00,
165
+ pointValue: 1000.00,
166
+ margin: 7000,
167
+ characteristics: {
168
+ volatility: 'high',
169
+ sector: 'energy',
170
+ behavior: 'news-driven, inventories, geopolitical, OPEC',
171
+ avgRange: '1.50-3.00 points'
172
+ },
173
+ correlations: {
174
+ positive: ['BZ', 'XLE'],
175
+ negative: [],
176
+ related: ['USO', 'WTI']
177
+ },
178
+ sessions: {
179
+ most_active: ['us_open', 'inventory_report'],
180
+ avoid: ['overnight_thin']
181
+ }
182
+ }
183
+ };
184
+
185
+ /**
186
+ * Trading sessions with times (Eastern Time)
187
+ */
188
+ const SESSIONS = {
189
+ asia_open: { start: '18:00', end: '20:00', description: 'Asia market open' },
190
+ asia_session: { start: '20:00', end: '03:00', description: 'Asia main session' },
191
+ asia_lunch: { start: '00:00', end: '01:00', description: 'Asia lunch (low volume)' },
192
+ london: { start: '03:00', end: '08:00', description: 'London session' },
193
+ london_us_overlap: { start: '08:00', end: '11:30', description: 'London/US overlap' },
194
+ us_open: { start: '09:30', end: '11:30', description: 'US market open (high volume)' },
195
+ us_midday: { start: '11:30', end: '14:00', description: 'US midday (lower volume)' },
196
+ us_afternoon: { start: '14:00', end: '15:00', description: 'US afternoon' },
197
+ us_close: { start: '15:00', end: '16:00', description: 'US close (rebalancing)' },
198
+ overnight: { start: '16:00', end: '18:00', description: 'Overnight transition' }
199
+ };
200
+
201
+ /**
202
+ * Get symbol data by ID
203
+ */
204
+ const getSymbol = (symbolId) => {
205
+ const key = symbolId?.toUpperCase?.()?.replace(/[0-9]/g, '') || '';
206
+ return SYMBOLS[key] || null;
207
+ };
208
+
209
+ /**
210
+ * Get current session based on time
211
+ */
212
+ const getCurrentSession = (date = new Date()) => {
213
+ const et = new Date(date.toLocaleString('en-US', { timeZone: 'America/New_York' }));
214
+ const hours = et.getHours();
215
+ const minutes = et.getMinutes();
216
+ const time = hours * 60 + minutes;
217
+
218
+ for (const [name, session] of Object.entries(SESSIONS)) {
219
+ const [startH, startM] = session.start.split(':').map(Number);
220
+ const [endH, endM] = session.end.split(':').map(Number);
221
+ const start = startH * 60 + startM;
222
+ const end = endH * 60 + endM;
223
+
224
+ if (start <= end) {
225
+ if (time >= start && time < end) return { name, ...session };
226
+ } else {
227
+ if (time >= start || time < end) return { name, ...session };
228
+ }
229
+ }
230
+ return { name: 'unknown', description: 'Unknown session' };
231
+ };
232
+
233
+ /**
234
+ * Check if current time is good for trading a symbol
235
+ */
236
+ const isGoodSessionForSymbol = (symbolId, date = new Date()) => {
237
+ const symbol = getSymbol(symbolId);
238
+ if (!symbol) return { good: true, reason: 'Unknown symbol' };
239
+
240
+ const session = getCurrentSession(date);
241
+
242
+ if (symbol.sessions.avoid?.includes(session.name)) {
243
+ return { good: false, reason: `${session.description} - typically low volume for ${symbolId}` };
244
+ }
245
+
246
+ if (symbol.sessions.most_active?.includes(session.name)) {
247
+ return { good: true, reason: `${session.description} - optimal for ${symbolId}` };
248
+ }
249
+
250
+ return { good: true, reason: session.description };
251
+ };
252
+
253
+ module.exports = {
254
+ SYMBOLS,
255
+ SESSIONS,
256
+ getSymbol,
257
+ getCurrentSession,
258
+ isGoodSessionForSymbol
259
+ };
@@ -0,0 +1,256 @@
1
+ /**
2
+ * CLIProxy Service
3
+ *
4
+ * Provides OAuth connections to paid AI plans (Claude Pro, ChatGPT Plus, etc.)
5
+ * via the embedded CLIProxyAPI binary.
6
+ */
7
+
8
+ const http = require('http');
9
+ const manager = require('./manager');
10
+
11
+ // Re-export manager functions
12
+ const {
13
+ CLIPROXY_VERSION,
14
+ INSTALL_DIR,
15
+ AUTH_DIR,
16
+ DEFAULT_PORT,
17
+ CALLBACK_PORTS,
18
+ CALLBACK_PATHS,
19
+ isInstalled,
20
+ isHeadless,
21
+ install,
22
+ isRunning,
23
+ start,
24
+ stop,
25
+ ensureRunning,
26
+ getLoginUrl,
27
+ getCallbackPort,
28
+ processCallback
29
+ } = manager;
30
+
31
+ // Internal API key (must match config.yaml)
32
+ const API_KEY = 'hqx-internal-key';
33
+
34
+ /**
35
+ * Make HTTP request to local CLIProxyAPI
36
+ * @param {string} path - API path
37
+ * @param {string} method - HTTP method
38
+ * @param {Object} body - Request body (optional)
39
+ * @param {number} timeout - Timeout in ms (default 60000 per RULES.md #15)
40
+ * @returns {Promise<Object>} { success, data, error }
41
+ */
42
+ const fetchLocal = (path, method = 'GET', body = null, timeout = 60000) => {
43
+ return new Promise((resolve) => {
44
+ const options = {
45
+ hostname: '127.0.0.1',
46
+ port: DEFAULT_PORT,
47
+ path,
48
+ method,
49
+ headers: {
50
+ 'Content-Type': 'application/json',
51
+ 'Authorization': `Bearer ${API_KEY}`
52
+ },
53
+ timeout
54
+ };
55
+
56
+ const req = http.request(options, (res) => {
57
+ let data = '';
58
+ res.on('data', chunk => data += chunk);
59
+ res.on('end', () => {
60
+ try {
61
+ if (res.statusCode >= 200 && res.statusCode < 300) {
62
+ const parsed = data ? JSON.parse(data) : {};
63
+ resolve({ success: true, data: parsed, error: null });
64
+ } else {
65
+ resolve({ success: false, error: `HTTP ${res.statusCode}`, data: null });
66
+ }
67
+ } catch (error) {
68
+ resolve({ success: false, error: 'Invalid JSON response', data: null });
69
+ }
70
+ });
71
+ });
72
+
73
+ req.on('error', (error) => {
74
+ if (error.code === 'ECONNREFUSED') {
75
+ resolve({ success: false, error: 'CLIProxyAPI not running', data: null });
76
+ } else {
77
+ resolve({ success: false, error: error.message, data: null });
78
+ }
79
+ });
80
+
81
+ req.on('timeout', () => {
82
+ req.destroy();
83
+ resolve({ success: false, error: 'Request timeout', data: null });
84
+ });
85
+
86
+ if (body) {
87
+ req.write(JSON.stringify(body));
88
+ }
89
+
90
+ req.end();
91
+ });
92
+ };
93
+
94
+ /**
95
+ * Fetch available models from CLIProxyAPI
96
+ * @returns {Promise<Object>} { success, models, error }
97
+ */
98
+ const fetchModels = async () => {
99
+ const result = await fetchLocal('/v1/models');
100
+
101
+ if (!result.success) {
102
+ return { success: false, models: [], error: result.error };
103
+ }
104
+
105
+ const data = result.data;
106
+ if (!data || !data.data || !Array.isArray(data.data)) {
107
+ return { success: false, models: [], error: 'Invalid response format' };
108
+ }
109
+
110
+ const models = data.data
111
+ .filter(m => m.id)
112
+ .map(m => ({ id: m.id, name: m.id }));
113
+
114
+ if (models.length === 0) {
115
+ return { success: false, models: [], error: 'No models available' };
116
+ }
117
+
118
+ return { success: true, models, error: null };
119
+ };
120
+
121
+ /**
122
+ * Get provider-specific models
123
+ * @param {string} providerId - Provider ID
124
+ * @returns {Promise<Object>} { success, models, error }
125
+ */
126
+ const fetchProviderModels = async (providerId) => {
127
+ const result = await fetchModels();
128
+ if (!result.success) return result;
129
+
130
+ // Filter by provider prefix
131
+ const prefixMap = {
132
+ anthropic: 'claude',
133
+ openai: 'gpt',
134
+ google: 'gemini',
135
+ qwen: 'qwen'
136
+ };
137
+
138
+ const prefix = prefixMap[providerId];
139
+ if (!prefix) return result;
140
+
141
+ const filtered = result.models.filter(m =>
142
+ m.id.toLowerCase().includes(prefix)
143
+ );
144
+
145
+ // Return only filtered models for this provider (empty if none found)
146
+ return {
147
+ success: filtered.length > 0,
148
+ models: filtered,
149
+ error: filtered.length === 0 ? `No ${providerId} models available` : null
150
+ };
151
+ };
152
+
153
+ /**
154
+ * Check which providers have auth files (are connected)
155
+ * @returns {Object} { anthropic: true/false, google: true/false, openai: true/false, qwen: true/false }
156
+ */
157
+ const getConnectedProviders = () => {
158
+ const fs = require('fs');
159
+ const connected = { anthropic: false, google: false, openai: false, qwen: false };
160
+
161
+ try {
162
+ if (!fs.existsSync(AUTH_DIR)) return connected;
163
+
164
+ const files = fs.readdirSync(AUTH_DIR);
165
+ for (const file of files) {
166
+ if (file.startsWith('claude-') && file.endsWith('.json')) connected.anthropic = true;
167
+ if (file.startsWith('gemini-') && file.endsWith('.json')) connected.google = true;
168
+ if (file.startsWith('codex-') && file.endsWith('.json')) connected.openai = true;
169
+ if (file.startsWith('qwen-') && file.endsWith('.json')) connected.qwen = true;
170
+ }
171
+ } catch (e) { /* ignore */ }
172
+
173
+ return connected;
174
+ };
175
+
176
+ /**
177
+ * Chat completion request
178
+ * @param {string} model - Model ID
179
+ * @param {Array} messages - Chat messages
180
+ * @param {Object} options - Additional options
181
+ * @returns {Promise<Object>} { success, response, error }
182
+ */
183
+ const chatCompletion = async (model, messages, options = {}) => {
184
+ const body = {
185
+ model,
186
+ messages,
187
+ stream: false,
188
+ ...options
189
+ };
190
+
191
+ const result = await fetchLocal('/v1/chat/completions', 'POST', body);
192
+
193
+ if (!result.success) {
194
+ return { success: false, response: null, error: result.error };
195
+ }
196
+
197
+ return { success: true, response: result.data, error: null };
198
+ };
199
+
200
+ /**
201
+ * Simple chat function for AI supervision
202
+ * @param {string} providerId - Provider ID (anthropic, openai, google, etc.)
203
+ * @param {string} modelId - Model ID
204
+ * @param {string} prompt - User prompt
205
+ * @param {number} timeout - Timeout in ms
206
+ * @returns {Promise<Object>} { success, content, error }
207
+ */
208
+ const chat = async (providerId, modelId, prompt, timeout = 30000) => {
209
+ const messages = [{ role: 'user', content: prompt }];
210
+
211
+ const result = await fetchLocal('/v1/chat/completions', 'POST', {
212
+ model: modelId,
213
+ messages,
214
+ stream: false
215
+ }, timeout);
216
+
217
+ if (!result.success) {
218
+ return { success: false, content: null, error: result.error };
219
+ }
220
+
221
+ // Extract content from response
222
+ const data = result.data;
223
+ if (data?.choices?.[0]?.message?.content) {
224
+ return { success: true, content: data.choices[0].message.content, error: null };
225
+ }
226
+
227
+ return { success: false, content: null, error: 'No content in response' };
228
+ };
229
+
230
+ module.exports = {
231
+ // Manager
232
+ CLIPROXY_VERSION,
233
+ INSTALL_DIR,
234
+ AUTH_DIR,
235
+ DEFAULT_PORT,
236
+ CALLBACK_PORTS,
237
+ CALLBACK_PATHS,
238
+ isInstalled,
239
+ isHeadless,
240
+ install,
241
+ isRunning,
242
+ start,
243
+ stop,
244
+ ensureRunning,
245
+ getLoginUrl,
246
+ getCallbackPort,
247
+ processCallback,
248
+
249
+ // API
250
+ fetchLocal,
251
+ fetchModels,
252
+ fetchProviderModels,
253
+ getConnectedProviders,
254
+ chatCompletion,
255
+ chat
256
+ };
@@ -0,0 +1,111 @@
1
+ /**
2
+ * CLIProxyAPI Installer
3
+ *
4
+ * Handles downloading and extracting CLIProxyAPI binary
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const https = require('https');
9
+ const http = require('http');
10
+ const { createGunzip } = require('zlib');
11
+ const tar = require('tar');
12
+
13
+ /**
14
+ * Download file from URL with redirect support
15
+ * @param {string} url - URL to download
16
+ * @param {string} destPath - Destination path
17
+ * @param {Function} onProgress - Progress callback (percent)
18
+ * @returns {Promise<boolean>}
19
+ */
20
+ const downloadFile = (url, destPath, onProgress = null) => {
21
+ return new Promise((resolve, reject) => {
22
+ const file = fs.createWriteStream(destPath);
23
+
24
+ const request = (url.startsWith('https') ? https : http).get(url, (response) => {
25
+ // Handle redirects
26
+ if (response.statusCode === 302 || response.statusCode === 301) {
27
+ file.close();
28
+ fs.unlinkSync(destPath);
29
+ return downloadFile(response.headers.location, destPath, onProgress)
30
+ .then(resolve)
31
+ .catch(reject);
32
+ }
33
+
34
+ if (response.statusCode !== 200) {
35
+ file.close();
36
+ fs.unlinkSync(destPath);
37
+ return reject(new Error(`HTTP ${response.statusCode}`));
38
+ }
39
+
40
+ const totalSize = parseInt(response.headers['content-length'], 10);
41
+ let downloadedSize = 0;
42
+
43
+ response.on('data', (chunk) => {
44
+ downloadedSize += chunk.length;
45
+ if (onProgress && totalSize) {
46
+ onProgress(Math.round((downloadedSize / totalSize) * 100));
47
+ }
48
+ });
49
+
50
+ response.pipe(file);
51
+
52
+ file.on('finish', () => {
53
+ file.close();
54
+ resolve(true);
55
+ });
56
+ });
57
+
58
+ request.on('error', (err) => {
59
+ file.close();
60
+ if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
61
+ reject(err);
62
+ });
63
+
64
+ request.setTimeout(120000, () => {
65
+ request.destroy();
66
+ reject(new Error('Download timeout'));
67
+ });
68
+ });
69
+ };
70
+
71
+ /**
72
+ * Extract tar.gz file
73
+ * @param {string} archivePath - Path to archive
74
+ * @param {string} destDir - Destination directory
75
+ * @returns {Promise<boolean>}
76
+ */
77
+ const extractTarGz = (archivePath, destDir) => {
78
+ return new Promise((resolve, reject) => {
79
+ fs.createReadStream(archivePath)
80
+ .pipe(createGunzip())
81
+ .pipe(tar.extract({ cwd: destDir }))
82
+ .on('finish', () => resolve(true))
83
+ .on('error', reject);
84
+ });
85
+ };
86
+
87
+ /**
88
+ * Extract zip file (Windows)
89
+ * @param {string} archivePath - Path to archive
90
+ * @param {string} destDir - Destination directory
91
+ * @returns {Promise<boolean>}
92
+ */
93
+ const extractZip = async (archivePath, destDir) => {
94
+ const { execSync } = require('child_process');
95
+
96
+ if (process.platform === 'win32') {
97
+ execSync(`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force"`, {
98
+ stdio: 'ignore'
99
+ });
100
+ } else {
101
+ execSync(`unzip -o "${archivePath}" -d "${destDir}"`, { stdio: 'ignore' });
102
+ }
103
+
104
+ return true;
105
+ };
106
+
107
+ module.exports = {
108
+ downloadFile,
109
+ extractTarGz,
110
+ extractZip
111
+ };