hedgequantx 2.7.99 → 2.8.1

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.99",
3
+ "version": "2.8.1",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -242,18 +242,10 @@ const handleUpdate = async () => {
242
242
 
243
243
  spinner.succeed(`UPDATED TO V${latestVersion}!`);
244
244
  console.log(chalk.green('\n ✓ UPDATE SUCCESSFUL!'));
245
- console.log(chalk.cyan('\n RESTARTING HQX...'));
246
-
247
- // Restart CLI with new version using spawn detached
248
- const { spawn } = require('child_process');
249
- const child = spawn('hqx', [], {
250
- detached: true,
251
- stdio: 'inherit',
252
- shell: true
253
- });
254
- child.unref();
255
-
256
- // Exit current process to let new one take over
245
+ console.log(chalk.yellow('\n Please restart HQX to use the new version.'));
246
+ console.log(chalk.cyan(' Run: hqx'));
247
+ console.log();
248
+ await prompts.waitForEnter();
257
249
  process.exit(0);
258
250
 
259
251
  } catch (error) {
@@ -5,8 +5,10 @@
5
5
  */
6
6
 
7
7
  const chalk = require('chalk');
8
+ const ora = require('ora');
8
9
  const { centerText, visibleLength } = require('../ui');
9
10
  const cliproxy = require('../services/cliproxy');
11
+ const { runPreflightCheck, formatPreflightResults, getPreflightSummary } = require('../services/ai-supervision');
10
12
 
11
13
  /**
12
14
  * Draw a 2-column row with perfect alignment
@@ -294,10 +296,53 @@ const drawProviderWindow = (provider, config, boxWidth) => {
294
296
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
295
297
  };
296
298
 
299
+ /**
300
+ * Draw and run connection test for all agents
301
+ * @param {Array} agents - Array of agent configs
302
+ * @param {number} boxWidth - Box width
303
+ * @param {Function} clearWithBanner - Function to clear and show banner
304
+ * @returns {Promise<Object>} Test results
305
+ */
306
+ const drawConnectionTest = async (agents, boxWidth, clearWithBanner) => {
307
+ if (agents.length === 0) {
308
+ console.log(chalk.yellow('\n No agents configured. Connect an agent first.'));
309
+ return { success: false, error: 'No agents' };
310
+ }
311
+
312
+ clearWithBanner();
313
+ const W = boxWidth - 2;
314
+
315
+ console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
316
+ console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('AI AGENTS CONNECTION TEST', W)) + chalk.cyan('║'));
317
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
318
+ console.log(chalk.cyan('║') + ' '.repeat(W) + chalk.cyan('║'));
319
+
320
+ // Run pre-flight check
321
+ const spinner = ora({ text: 'Testing connections...', color: 'yellow' }).start();
322
+ const results = await runPreflightCheck(agents);
323
+ spinner.stop();
324
+
325
+ // Display results
326
+ const lines = formatPreflightResults(results, boxWidth);
327
+ for (const line of lines) {
328
+ const paddedLine = line.length < W - 1 ? line + ' '.repeat(W - 1 - visibleLength(line)) : line;
329
+ console.log(chalk.cyan('║') + ' ' + paddedLine + chalk.cyan('║'));
330
+ }
331
+
332
+ // Summary
333
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
334
+ const summary = getPreflightSummary(results);
335
+ console.log(chalk.cyan('║') + centerText(summary.text, W) + chalk.cyan('║'));
336
+ console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
337
+
338
+ return results;
339
+ };
340
+
297
341
  module.exports = {
298
342
  draw2ColRow,
299
343
  draw2ColTable,
300
344
  drawProvidersTable,
301
345
  drawModelsTable,
302
- drawProviderWindow
346
+ drawProviderWindow,
347
+ drawConnectionTest
303
348
  };
@@ -14,7 +14,7 @@ const ora = require('ora');
14
14
  const { getLogoWidth, displayBanner } = require('../ui');
15
15
  const { prompts } = require('../utils');
16
16
  const { fetchModelsFromApi } = require('./ai-models');
17
- const { drawProvidersTable, drawModelsTable, drawProviderWindow } = require('./ai-agents-ui');
17
+ const { drawProvidersTable, drawModelsTable, drawProviderWindow, drawConnectionTest } = require('./ai-agents-ui');
18
18
  const cliproxy = require('../services/cliproxy');
19
19
 
20
20
  /** Clear screen and show banner (always closed) */
@@ -102,70 +102,46 @@ const activateProvider = (config, providerId, data) => {
102
102
  };
103
103
 
104
104
  /** Wait for child process to exit */
105
- const waitForProcessExit = (childProcess, timeoutMs = 15000, intervalMs = 500) => {
106
- return new Promise((resolve) => {
107
- if (!childProcess) return resolve();
108
- let elapsed = 0;
109
- const checkInterval = setInterval(() => {
110
- elapsed += intervalMs;
111
- if (childProcess.exitCode !== null || childProcess.killed || elapsed >= timeoutMs) {
112
- clearInterval(checkInterval);
113
- if (elapsed >= timeoutMs) try { childProcess.kill(); } catch (e) { /* ignore */ }
114
- resolve();
115
- }
116
- }, intervalMs);
117
- });
118
- };
105
+ const waitForProcessExit = (cp, timeoutMs = 15000, intervalMs = 500) => new Promise((resolve) => {
106
+ if (!cp) return resolve();
107
+ let elapsed = 0;
108
+ const check = setInterval(() => {
109
+ elapsed += intervalMs;
110
+ if (cp.exitCode !== null || cp.killed || elapsed >= timeoutMs) {
111
+ clearInterval(check);
112
+ if (elapsed >= timeoutMs) try { cp.kill(); } catch (e) {}
113
+ resolve();
114
+ }
115
+ }, intervalMs);
116
+ });
119
117
 
120
118
  /** Handle CLIProxy connection (with auto-install) */
121
119
  const handleCliProxyConnection = async (provider, config, boxWidth) => {
122
120
  console.log();
123
-
124
- // Check if CLIProxyAPI is installed
121
+ // Check/install CLIProxyAPI
125
122
  if (!cliproxy.isInstalled()) {
126
123
  console.log(chalk.yellow(' CLIPROXYAPI NOT INSTALLED. INSTALLING...'));
127
- const spinner = ora({ text: 'DOWNLOADING CLIPROXYAPI...', color: 'yellow' }).start();
128
-
129
- const installResult = await cliproxy.install((msg, percent) => {
130
- spinner.text = `${msg.toUpperCase()} ${percent}%`;
131
- });
132
-
133
- if (!installResult.success) {
134
- spinner.fail(`INSTALLATION FAILED: ${installResult.error.toUpperCase()}`);
135
- await prompts.waitForEnter();
136
- return false;
137
- }
124
+ const spinner = ora({ text: 'DOWNLOADING...', color: 'yellow' }).start();
125
+ const installResult = await cliproxy.install((msg, percent) => { spinner.text = `${msg.toUpperCase()} ${percent}%`; });
126
+ if (!installResult.success) { spinner.fail(`INSTALL FAILED: ${installResult.error}`); await prompts.waitForEnter(); return false; }
138
127
  spinner.succeed('CLIPROXYAPI INSTALLED');
139
128
  }
140
-
141
- // Check if running, start if not (or restart if config missing)
129
+ // Check/start CLIProxy
142
130
  let status = await cliproxy.isRunning();
143
131
  if (!status.running) {
144
132
  const spinner = ora({ text: 'STARTING CLIPROXYAPI...', color: 'yellow' }).start();
145
133
  const startResult = await cliproxy.start();
146
-
147
- if (!startResult.success) {
148
- spinner.fail(`FAILED TO START: ${startResult.error.toUpperCase()}`);
149
- await prompts.waitForEnter();
150
- return false;
151
- }
134
+ if (!startResult.success) { spinner.fail(`START FAILED: ${startResult.error}`); await prompts.waitForEnter(); return false; }
152
135
  spinner.succeed('CLIPROXYAPI STARTED');
153
136
  } else {
154
- // Running, but check if config exists (for proper API key auth)
155
- const configPath = require('path').join(require('os').homedir(), '.hqx', 'cliproxy', 'config.yaml');
156
- if (!require('fs').existsSync(configPath)) {
157
- console.log(chalk.yellow(' RESTARTING CLIPROXYAPI WITH PROPER CONFIG...'));
137
+ const cfgPath = path.join(os.homedir(), '.hqx', 'cliproxy', 'config.yaml');
138
+ if (!fs.existsSync(cfgPath)) {
139
+ console.log(chalk.yellow(' RESTARTING CLIPROXYAPI...'));
158
140
  await cliproxy.stop();
159
- const startResult = await cliproxy.start();
160
- if (!startResult.success) {
161
- console.log(chalk.red(` FAILED TO RESTART: ${startResult.error.toUpperCase()}`));
162
- await prompts.waitForEnter();
163
- return false;
164
- }
165
- console.log(chalk.green(' ✓ CLIPROXYAPI RESTARTED'));
166
- } else {
167
- console.log(chalk.green(' ✓ CLIPROXYAPI IS RUNNING'));
168
- }
141
+ const res = await cliproxy.start();
142
+ if (!res.success) { console.log(chalk.red(` RESTART FAILED: ${res.error}`)); await prompts.waitForEnter(); return false; }
143
+ console.log(chalk.green(' RESTARTED'));
144
+ } else console.log(chalk.green(' ✓ CLIPROXYAPI RUNNING'));
169
145
  }
170
146
 
171
147
  // First, check if models are already available (existing auth)
@@ -397,7 +373,7 @@ const handleProviderConfig = async (provider, config) => {
397
373
  return config;
398
374
  };
399
375
 
400
- /** Get active AI provider config */
376
+ /** Get active AI provider config (legacy - single provider) */
401
377
  const getActiveProvider = () => {
402
378
  const config = loadConfig();
403
379
  for (const provider of AI_PROVIDERS) {
@@ -409,15 +385,52 @@ const getActiveProvider = () => {
409
385
  connectionType: pc.connectionType,
410
386
  apiKey: pc.apiKey || null,
411
387
  modelId: pc.modelId || null,
412
- modelName: pc.modelName || null
388
+ modelName: pc.modelName || null,
389
+ weight: pc.weight || 100
413
390
  };
414
391
  }
415
392
  }
416
393
  return null;
417
394
  };
418
395
 
396
+ /** Get ALL active AI agents for multi-agent supervision */
397
+ const getActiveAgents = () => {
398
+ const config = loadConfig();
399
+ const agents = [];
400
+
401
+ for (const provider of AI_PROVIDERS) {
402
+ const pc = config.providers[provider.id];
403
+ if (pc && pc.active) {
404
+ agents.push({
405
+ id: `agent-${provider.id}`,
406
+ provider: provider.id,
407
+ name: provider.name,
408
+ connectionType: pc.connectionType,
409
+ apiKey: pc.apiKey || null,
410
+ modelId: pc.modelId || null,
411
+ modelName: pc.modelName || null,
412
+ weight: pc.weight || Math.floor(100 / AI_PROVIDERS.filter(p => config.providers[p.id]?.active).length),
413
+ active: true
414
+ });
415
+ }
416
+ }
417
+
418
+ return agents;
419
+ };
420
+
421
+ /** Get supervision config for SupervisionEngine */
422
+ const getSupervisionConfig = () => {
423
+ const agents = getActiveAgents();
424
+ return {
425
+ supervisionEnabled: agents.length > 0,
426
+ agents,
427
+ minAgents: 1,
428
+ timeout: 30000
429
+ };
430
+ };
431
+
419
432
  /** Count active AI agents */
420
- const getActiveAgentCount = () => getActiveProvider() ? 1 : 0;
433
+ const getActiveAgentCount = () => getActiveAgents().length;
421
434
 
422
435
  /** Main AI Agents menu */
423
436
  const aiAgentsMenu = async () => {
@@ -428,11 +441,26 @@ const aiAgentsMenu = async () => {
428
441
  clearWithBanner();
429
442
  drawProvidersTable(AI_PROVIDERS, config, boxWidth);
430
443
 
431
- const input = await prompts.textInput(chalk.cyan('SELECT (1-8/B): '));
444
+ // Show [T] TEST option if agents are configured
445
+ const agentCount = getActiveAgentCount();
446
+ if (agentCount > 0) {
447
+ console.log(chalk.cyan(' [T] TEST ALL CONNECTIONS'));
448
+ }
449
+ console.log();
450
+
451
+ const promptText = agentCount > 0 ? 'SELECT (1-8/T/B): ' : 'SELECT (1-8/B): ';
452
+ const input = await prompts.textInput(chalk.cyan(promptText));
432
453
  const choice = (input || '').toLowerCase().trim();
433
454
 
434
455
  if (choice === 'b' || choice === '') break;
435
456
 
457
+ if (choice === 't' && agentCount > 0) {
458
+ const agents = getActiveAgents();
459
+ await drawConnectionTest(agents, boxWidth, clearWithBanner);
460
+ await prompts.waitForEnter();
461
+ continue;
462
+ }
463
+
436
464
  const num = parseInt(choice);
437
465
  if (!isNaN(num) && num >= 1 && num <= AI_PROVIDERS.length) {
438
466
  config = await handleProviderConfig(AI_PROVIDERS[num - 1], config);
@@ -447,6 +475,8 @@ const aiAgentsMenu = async () => {
447
475
  module.exports = {
448
476
  aiAgentsMenu,
449
477
  getActiveProvider,
478
+ getActiveAgents,
479
+ getSupervisionConfig,
450
480
  getActiveAgentCount,
451
481
  loadConfig,
452
482
  saveConfig,
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Algo Executor - Shared execution engine for all algo modes
3
3
  * Handles market data, signals, orders, and P&L tracking
4
+ * Supports multi-agent AI supervision for signal optimization
4
5
  */
5
6
 
6
7
  const readline = require('readline');
7
8
  const { AlgoUI, renderSessionSummary } = require('./ui');
8
9
  const { M1 } = require('../../lib/m/s1');
9
10
  const { MarketDataFeed } = require('../../lib/data');
11
+ const { SupervisionEngine } = require('../../services/ai-supervision');
10
12
 
11
13
  /**
12
14
  * Execute algo strategy with market data
@@ -15,11 +17,15 @@ const { MarketDataFeed } = require('../../lib/data');
15
17
  * @param {Object} params.account - Account object
16
18
  * @param {Object} params.contract - Contract object
17
19
  * @param {Object} params.config - Algo config (contracts, target, risk, showName)
18
- * @param {Object} params.options - Optional: aiSupervision, aiProvider, askAI function
20
+ * @param {Object} params.options - Optional: supervisionConfig for multi-agent AI
19
21
  */
20
22
  const executeAlgo = async ({ service, account, contract, config, options = {} }) => {
21
23
  const { contracts, dailyTarget, maxRisk, showName } = config;
22
- const { aiSupervision, aiProvider, askAI, subtitle } = options;
24
+ const { supervisionConfig, subtitle } = options;
25
+
26
+ // Initialize AI Supervision Engine if configured
27
+ const supervisionEnabled = supervisionConfig?.supervisionEnabled && supervisionConfig?.agents?.length > 0;
28
+ const supervisionEngine = supervisionEnabled ? new SupervisionEngine(supervisionConfig) : null;
23
29
 
24
30
  const accountName = showName
25
31
  ? (account.accountName || account.rithmicAccountId || account.accountId)
@@ -29,7 +35,7 @@ const executeAlgo = async ({ service, account, contract, config, options = {} })
29
35
  const tickSize = contract.tickSize || 0.25;
30
36
 
31
37
  const ui = new AlgoUI({
32
- subtitle: subtitle || (aiSupervision ? 'CUSTOM STRATEGY + AI' : 'HQX Ultra Scalping'),
38
+ subtitle: subtitle || (supervisionEnabled ? 'HQX + AI SUPERVISION' : 'HQX Ultra Scalping'),
33
39
  mode: 'one-account'
34
40
  });
35
41
 
@@ -57,8 +63,8 @@ const executeAlgo = async ({ service, account, contract, config, options = {} })
57
63
  let pendingOrder = false;
58
64
  let tickCount = 0;
59
65
 
60
- // AI context for supervision
61
- const aiContext = { recentTicks: [], recentSignals: [], maxTicks: 100 };
66
+ // Context for AI supervision
67
+ const aiContext = { recentTicks: [], recentSignals: [], recentTrades: [], maxTicks: 100 };
62
68
 
63
69
  // Initialize Strategy
64
70
  const strategy = new M1({ tickSize });
@@ -68,32 +74,68 @@ const executeAlgo = async ({ service, account, contract, config, options = {} })
68
74
  const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
69
75
 
70
76
  // Log startup
71
- ui.addLog('info', `Strategy: ${aiSupervision ? 'CUSTOM + AI' : 'HQX Ultra Scalping'}`);
77
+ ui.addLog('info', `Strategy: ${supervisionEnabled ? 'HQX + AI Supervision' : 'HQX Ultra Scalping'}`);
72
78
  ui.addLog('info', `Account: ${accountName}`);
73
79
  ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
74
80
  ui.addLog('info', `Target: $${dailyTarget} | Risk: $${maxRisk}`);
75
- if (aiSupervision && aiProvider) ui.addLog('info', `AI: ${aiProvider.name} supervision`);
81
+ if (supervisionEnabled) {
82
+ const agentCount = supervisionEngine.getActiveCount();
83
+ ui.addLog('info', `AI Agents: ${agentCount} active`);
84
+ }
76
85
  ui.addLog('info', 'Connecting to market data...');
77
86
 
78
87
  // Handle strategy signals
79
88
  strategy.on('signal', async (signal) => {
80
89
  if (!running || pendingOrder || currentPosition !== 0) return;
81
90
 
82
- const { direction, entry, stopLoss, takeProfit, confidence } = signal;
91
+ let { direction, entry, stopLoss, takeProfit, confidence } = signal;
92
+ let orderSize = contracts;
83
93
 
84
94
  aiContext.recentSignals.push({ ...signal, timestamp: Date.now() });
85
95
  if (aiContext.recentSignals.length > 10) aiContext.recentSignals.shift();
86
96
 
87
97
  ui.addLog('info', `Signal: ${direction.toUpperCase()} @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
88
98
 
89
- // AI Supervision check
90
- if (aiSupervision && askAI) {
91
- const aiDecision = await askAI(aiContext, signal, { symbolName, currentPosition, stats, dailyTarget, maxRisk });
92
- if (!aiDecision.approve) {
93
- ui.addLog('info', `AI rejected: ${aiDecision.reason || 'No reason'}`);
99
+ // Multi-Agent AI Supervision
100
+ if (supervisionEnabled && supervisionEngine) {
101
+ ui.addLog('info', 'AI analyzing signal...');
102
+
103
+ const supervisionResult = await supervisionEngine.supervise({
104
+ symbolId: symbolName,
105
+ signal: { direction, entry, stopLoss, takeProfit, confidence, size: contracts },
106
+ recentTicks: aiContext.recentTicks,
107
+ recentSignals: aiContext.recentSignals,
108
+ recentTrades: aiContext.recentTrades,
109
+ stats,
110
+ config: { dailyTarget, maxRisk }
111
+ });
112
+
113
+ if (!supervisionResult.success) {
114
+ ui.addLog('info', `AI: ${supervisionResult.reason || 'Error'}`);
115
+ } else if (supervisionResult.decision === 'reject') {
116
+ ui.addLog('info', `AI rejected (${supervisionResult.confidence}%): ${supervisionResult.reason}`);
94
117
  return;
118
+ } else {
119
+ // Apply optimizations
120
+ const opt = supervisionResult.optimizedSignal;
121
+ if (opt.aiOptimized) {
122
+ if (opt.entry !== entry) entry = opt.entry;
123
+ if (opt.stopLoss !== stopLoss) stopLoss = opt.stopLoss;
124
+ if (opt.takeProfit !== takeProfit) takeProfit = opt.takeProfit;
125
+ if (opt.size && opt.size !== contracts) orderSize = opt.size;
126
+ }
127
+ const action = supervisionResult.decision === 'modify' ? 'optimized' : 'approved';
128
+ ui.addLog('info', `AI ${action} (${supervisionResult.confidence}%): ${supervisionResult.reason}`);
129
+
130
+ // Check timing
131
+ if (opt.aiTiming === 'wait') {
132
+ ui.addLog('info', 'AI: Wait for better entry');
133
+ return;
134
+ } else if (opt.aiTiming === 'cancel') {
135
+ ui.addLog('info', 'AI: Signal cancelled');
136
+ return;
137
+ }
95
138
  }
96
- ui.addLog('info', `AI approved: ${aiDecision.reason || 'OK'}`);
97
139
  }
98
140
 
99
141
  // Place order
@@ -105,24 +147,24 @@ const executeAlgo = async ({ service, account, contract, config, options = {} })
105
147
  contractId: contractId,
106
148
  type: 2,
107
149
  side: orderSide,
108
- size: contracts
150
+ size: orderSize
109
151
  });
110
152
 
111
153
  if (orderResult.success) {
112
- currentPosition = direction === 'long' ? contracts : -contracts;
154
+ currentPosition = direction === 'long' ? orderSize : -orderSize;
113
155
  stats.trades++;
114
156
  ui.addLog('fill_' + (direction === 'long' ? 'buy' : 'sell'),
115
- `OPENED ${direction.toUpperCase()} ${contracts}x @ market`);
157
+ `OPENED ${direction.toUpperCase()} ${orderSize}x @ market`);
116
158
 
117
159
  // Bracket orders
118
160
  if (stopLoss && takeProfit) {
119
161
  await service.placeOrder({
120
162
  accountId: account.accountId, contractId, type: 4,
121
- side: direction === 'long' ? 1 : 0, size: contracts, stopPrice: stopLoss
163
+ side: direction === 'long' ? 1 : 0, size: orderSize, stopPrice: stopLoss
122
164
  });
123
165
  await service.placeOrder({
124
166
  accountId: account.accountId, contractId, type: 1,
125
- side: direction === 'long' ? 1 : 0, size: contracts, limitPrice: takeProfit
167
+ side: direction === 'long' ? 1 : 0, size: orderSize, limitPrice: takeProfit
126
168
  });
127
169
  ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
128
170
  }