hedgequantx 2.5.15 → 2.5.16

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.5.15",
3
+ "version": "2.5.16",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -22,6 +22,9 @@ const dashboardMenu = async (service) => {
22
22
  const boxWidth = getLogoWidth();
23
23
  const W = boxWidth - 2;
24
24
 
25
+ // Check AI connection status
26
+ const aiConnected = aiService.isConnected();
27
+
25
28
  const makeLine = (content, align = 'left') => {
26
29
  const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
27
30
  const padding = W - plainLen;
@@ -330,12 +330,33 @@ const launchCopyTrading = async (config) => {
330
330
  if (!existing && pos.quantity !== 0) {
331
331
  // New position opened - copy to follower
332
332
  const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
333
+ const orderSide = pos.quantity > 0 ? 0 : 1; // 0=Buy, 1=Sell
333
334
  const symbol = pos.symbol || pos.contractId;
334
335
  const size = Math.abs(pos.quantity);
335
336
  const entry = pos.averagePrice || 0;
337
+
336
338
  algoLogger.positionOpened(ui, symbol, side, size, entry);
337
339
  algoLogger.info(ui, 'COPYING TO FOLLOWER', `${side} ${size}x ${symbol}`);
338
- // TODO: Place order on follower account
340
+
341
+ // Place order on follower account
342
+ try {
343
+ const orderResult = await follower.service.placeOrder({
344
+ accountId: follower.account.accountId,
345
+ contractId: pos.contractId,
346
+ type: 2, // Market order
347
+ side: orderSide,
348
+ size: follower.contracts
349
+ });
350
+
351
+ if (orderResult.success) {
352
+ algoLogger.orderFilled(ui, symbol, side, follower.contracts, entry);
353
+ algoLogger.info(ui, 'FOLLOWER ORDER', `${side} ${follower.contracts}x filled`);
354
+ } else {
355
+ algoLogger.orderRejected(ui, symbol, orderResult.error || 'Order failed');
356
+ }
357
+ } catch (err) {
358
+ algoLogger.error(ui, 'FOLLOWER ORDER FAILED', err.message);
359
+ }
339
360
  }
340
361
  }
341
362
 
@@ -344,13 +365,44 @@ const launchCopyTrading = async (config) => {
344
365
  const stillOpen = currentPositions.find(p => p.contractId === oldPos.contractId);
345
366
  if (!stillOpen || stillOpen.quantity === 0) {
346
367
  const side = oldPos.quantity > 0 ? 'LONG' : 'SHORT';
368
+ const closeSide = oldPos.quantity > 0 ? 1 : 0; // Opposite side to close
347
369
  const symbol = oldPos.symbol || oldPos.contractId;
348
370
  const size = Math.abs(oldPos.quantity);
349
371
  const exit = stillOpen?.averagePrice || oldPos.averagePrice || 0;
350
372
  const pnl = oldPos.profitAndLoss || 0;
373
+
351
374
  algoLogger.positionClosed(ui, symbol, side, size, exit, pnl);
352
375
  algoLogger.info(ui, 'CLOSING ON FOLLOWER', symbol);
353
- // TODO: Close position on follower account
376
+
377
+ // Close position on follower account
378
+ try {
379
+ // First try closePosition API
380
+ const closeResult = await follower.service.closePosition(
381
+ follower.account.accountId,
382
+ oldPos.contractId
383
+ );
384
+
385
+ if (closeResult.success) {
386
+ algoLogger.info(ui, 'FOLLOWER CLOSED', `${symbol} position closed`);
387
+ } else {
388
+ // Fallback: place market order to close
389
+ const orderResult = await follower.service.placeOrder({
390
+ accountId: follower.account.accountId,
391
+ contractId: oldPos.contractId,
392
+ type: 2, // Market order
393
+ side: closeSide,
394
+ size: follower.contracts
395
+ });
396
+
397
+ if (orderResult.success) {
398
+ algoLogger.info(ui, 'FOLLOWER CLOSED', `${symbol} via market order`);
399
+ } else {
400
+ algoLogger.error(ui, 'FOLLOWER CLOSE FAILED', orderResult.error || 'Close failed');
401
+ }
402
+ }
403
+ } catch (err) {
404
+ algoLogger.error(ui, 'FOLLOWER CLOSE ERROR', err.message);
405
+ }
354
406
  }
355
407
  }
356
408
 
@@ -182,7 +182,7 @@ const startAISupervised = async (service, agent) => {
182
182
 
183
183
  await prompts.waitForEnter();
184
184
 
185
- // Start the algo (placeholder for real implementation)
185
+ // Launch algo trading with AI supervision active
186
186
  return await oneAccountMenu(service);
187
187
 
188
188
  } else {
@@ -14,6 +14,8 @@ const asciichart = require('asciichart');
14
14
  const { connections } = require('../services');
15
15
  const { getLogoWidth, visibleLength, drawBoxHeader, drawBoxFooter, getColWidths, draw2ColHeader, draw2ColSeparator, fmtRow } = require('../ui');
16
16
  const { prompts } = require('../utils');
17
+ const aiService = require('../services/ai');
18
+ const AISupervisor = require('../services/ai/supervisor');
17
19
 
18
20
  /**
19
21
  * Show Stats Page
@@ -383,6 +385,56 @@ const showStats = async (service) => {
383
385
 
384
386
  drawBoxFooter(boxWidth);
385
387
 
388
+ // ========== AI SUPERVISION ==========
389
+ const aiAgents = aiService.getAgents();
390
+ const supervisionStatus = AISupervisor.getAllStatus();
391
+
392
+ if (aiAgents.length > 0) {
393
+ console.log();
394
+ drawBoxHeader('AI SUPERVISION', boxWidth);
395
+ draw2ColHeader('AGENTS', 'PERFORMANCE', boxWidth);
396
+
397
+ // Agent info
398
+ const activeAgent = aiService.getActiveAgent();
399
+ const agentNames = aiAgents.map(a => a.name).join(', ');
400
+ const agentMode = aiAgents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
401
+ const modeColor = aiAgents.length >= 2 ? chalk.magenta : chalk.cyan;
402
+
403
+ // Supervision metrics
404
+ let totalDecisions = 0;
405
+ let totalInterventions = 0;
406
+ let totalOptimizations = 0;
407
+ let totalRiskWarnings = 0;
408
+ let totalSessionTime = 0;
409
+
410
+ for (const status of supervisionStatus) {
411
+ if (status.active) {
412
+ totalDecisions += status.metrics?.totalDecisions || 0;
413
+ totalInterventions += status.metrics?.interventions || 0;
414
+ totalOptimizations += status.metrics?.optimizations || 0;
415
+ totalRiskWarnings += status.metrics?.riskWarnings || 0;
416
+ totalSessionTime += status.duration || 0;
417
+ }
418
+ }
419
+
420
+ const sessionTimeStr = totalSessionTime > 0
421
+ ? Math.floor(totalSessionTime / 60000) + 'm ' + Math.floor((totalSessionTime % 60000) / 1000) + 's'
422
+ : 'INACTIVE';
423
+
424
+ // Get real supervision data
425
+ const supervisionData = AISupervisor.getAggregatedData();
426
+ const supervisedAccounts = supervisionData.totalAccounts;
427
+ const supervisedPnL = supervisionData.totalPnL;
428
+
429
+ console.log(chalk.cyan('\u2551') + fmtRow('CONNECTED AGENTS:', chalk.green(String(aiAgents.length)), col1) + chalk.cyan('\u2502') + fmtRow('SUPERVISED ACCOUNTS:', supervisedAccounts > 0 ? chalk.white(String(supervisedAccounts)) : chalk.gray('0'), col2) + chalk.cyan('\u2551'));
430
+ console.log(chalk.cyan('\u2551') + fmtRow('MODE:', modeColor(agentMode), col1) + chalk.cyan('\u2502') + fmtRow('SUPERVISED P&L:', supervisedPnL !== 0 ? (supervisedPnL >= 0 ? chalk.green('$' + supervisedPnL.toFixed(2)) : chalk.red('$' + supervisedPnL.toFixed(2))) : chalk.gray('$0.00'), col2) + chalk.cyan('\u2551'));
431
+ console.log(chalk.cyan('\u2551') + fmtRow('ACTIVE:', activeAgent ? chalk.green(activeAgent.name) : chalk.gray('NONE'), col1) + chalk.cyan('\u2502') + fmtRow('POSITIONS:', chalk.white(String(supervisionData.totalPositions)), col2) + chalk.cyan('\u2551'));
432
+ console.log(chalk.cyan('\u2551') + fmtRow('SESSION TIME:', chalk.white(sessionTimeStr), col1) + chalk.cyan('\u2502') + fmtRow('OPEN ORDERS:', chalk.white(String(supervisionData.totalOrders)), col2) + chalk.cyan('\u2551'));
433
+ console.log(chalk.cyan('\u2551') + fmtRow('AGENTS:', chalk.gray(agentNames.substring(0, col1 - 10)), col1) + chalk.cyan('\u2502') + fmtRow('TRADES TODAY:', chalk.white(String(supervisionData.totalTrades)), col2) + chalk.cyan('\u2551'));
434
+
435
+ drawBoxFooter(boxWidth);
436
+ }
437
+
386
438
  // ========== EQUITY CURVE ==========
387
439
  console.log();
388
440
  drawBoxHeader('EQUITY CURVE', boxWidth);
@@ -0,0 +1,281 @@
1
+ /**
2
+ * AI Client - Makes real API calls to AI providers
3
+ *
4
+ * STRICT RULE: No mock responses. Real API calls only.
5
+ * If API fails → return null, not fake data.
6
+ */
7
+
8
+ const https = require('https');
9
+ const http = require('http');
10
+ const { getProvider } = require('./providers');
11
+
12
+ /**
13
+ * Make HTTP request to AI provider
14
+ * @param {string} url - Full URL
15
+ * @param {Object} options - Request options
16
+ * @returns {Promise<Object>} Response data
17
+ */
18
+ const makeRequest = (url, options) => {
19
+ return new Promise((resolve, reject) => {
20
+ const parsedUrl = new URL(url);
21
+ const protocol = parsedUrl.protocol === 'https:' ? https : http;
22
+
23
+ const req = protocol.request(url, {
24
+ method: options.method || 'POST',
25
+ headers: options.headers || {},
26
+ timeout: options.timeout || 30000
27
+ }, (res) => {
28
+ let data = '';
29
+ res.on('data', chunk => data += chunk);
30
+ res.on('end', () => {
31
+ try {
32
+ const json = JSON.parse(data);
33
+ if (res.statusCode >= 200 && res.statusCode < 300) {
34
+ resolve(json);
35
+ } else {
36
+ reject(new Error(json.error?.message || `HTTP ${res.statusCode}`));
37
+ }
38
+ } catch (e) {
39
+ reject(new Error(`Invalid JSON response: ${data.substring(0, 100)}`));
40
+ }
41
+ });
42
+ });
43
+
44
+ req.on('error', reject);
45
+ req.on('timeout', () => reject(new Error('Request timeout')));
46
+
47
+ if (options.body) {
48
+ req.write(JSON.stringify(options.body));
49
+ }
50
+ req.end();
51
+ });
52
+ };
53
+
54
+ /**
55
+ * Call OpenAI-compatible API
56
+ * Works with: OpenAI, Groq, Together, DeepSeek, Mistral, xAI, etc.
57
+ * @param {Object} agent - Agent configuration
58
+ * @param {string} prompt - User prompt
59
+ * @param {string} systemPrompt - System prompt
60
+ * @returns {Promise<string|null>} Response text or null on error
61
+ */
62
+ const callOpenAICompatible = async (agent, prompt, systemPrompt) => {
63
+ const provider = getProvider(agent.providerId);
64
+ if (!provider) return null;
65
+
66
+ const endpoint = agent.credentials?.endpoint || provider.endpoint;
67
+ const apiKey = agent.credentials?.apiKey;
68
+ const model = agent.model || provider.defaultModel;
69
+
70
+ if (!apiKey && provider.category !== 'local') {
71
+ return null;
72
+ }
73
+
74
+ const url = `${endpoint}/chat/completions`;
75
+
76
+ const headers = {
77
+ 'Content-Type': 'application/json'
78
+ };
79
+
80
+ if (apiKey) {
81
+ headers['Authorization'] = `Bearer ${apiKey}`;
82
+ }
83
+
84
+ // OpenRouter requires additional headers
85
+ if (agent.providerId === 'openrouter') {
86
+ headers['HTTP-Referer'] = 'https://hedgequantx.com';
87
+ headers['X-Title'] = 'HQX-CLI';
88
+ }
89
+
90
+ const body = {
91
+ model,
92
+ messages: [
93
+ { role: 'system', content: systemPrompt },
94
+ { role: 'user', content: prompt }
95
+ ],
96
+ temperature: 0.3,
97
+ max_tokens: 500
98
+ };
99
+
100
+ try {
101
+ const response = await makeRequest(url, { headers, body, timeout: 30000 });
102
+ return response.choices?.[0]?.message?.content || null;
103
+ } catch (error) {
104
+ return null;
105
+ }
106
+ };
107
+
108
+ /**
109
+ * Call Anthropic Claude API
110
+ * @param {Object} agent - Agent configuration
111
+ * @param {string} prompt - User prompt
112
+ * @param {string} systemPrompt - System prompt
113
+ * @returns {Promise<string|null>} Response text or null on error
114
+ */
115
+ const callAnthropic = async (agent, prompt, systemPrompt) => {
116
+ const provider = getProvider('anthropic');
117
+ if (!provider) return null;
118
+
119
+ const apiKey = agent.credentials?.apiKey;
120
+ const model = agent.model || provider.defaultModel;
121
+
122
+ if (!apiKey) return null;
123
+
124
+ const url = `${provider.endpoint}/messages`;
125
+
126
+ const headers = {
127
+ 'Content-Type': 'application/json',
128
+ 'x-api-key': apiKey,
129
+ 'anthropic-version': '2023-06-01'
130
+ };
131
+
132
+ const body = {
133
+ model,
134
+ max_tokens: 500,
135
+ system: systemPrompt,
136
+ messages: [
137
+ { role: 'user', content: prompt }
138
+ ]
139
+ };
140
+
141
+ try {
142
+ const response = await makeRequest(url, { headers, body, timeout: 30000 });
143
+ return response.content?.[0]?.text || null;
144
+ } catch (error) {
145
+ return null;
146
+ }
147
+ };
148
+
149
+ /**
150
+ * Call Google Gemini API
151
+ * @param {Object} agent - Agent configuration
152
+ * @param {string} prompt - User prompt
153
+ * @param {string} systemPrompt - System prompt
154
+ * @returns {Promise<string|null>} Response text or null on error
155
+ */
156
+ const callGemini = async (agent, prompt, systemPrompt) => {
157
+ const provider = getProvider('gemini');
158
+ if (!provider) return null;
159
+
160
+ const apiKey = agent.credentials?.apiKey;
161
+ const model = agent.model || provider.defaultModel;
162
+
163
+ if (!apiKey) return null;
164
+
165
+ const url = `${provider.endpoint}/models/${model}:generateContent?key=${apiKey}`;
166
+
167
+ const headers = {
168
+ 'Content-Type': 'application/json'
169
+ };
170
+
171
+ const body = {
172
+ contents: [
173
+ { role: 'user', parts: [{ text: `${systemPrompt}\n\n${prompt}` }] }
174
+ ],
175
+ generationConfig: {
176
+ temperature: 0.3,
177
+ maxOutputTokens: 500
178
+ }
179
+ };
180
+
181
+ try {
182
+ const response = await makeRequest(url, { headers, body, timeout: 30000 });
183
+ return response.candidates?.[0]?.content?.parts?.[0]?.text || null;
184
+ } catch (error) {
185
+ return null;
186
+ }
187
+ };
188
+
189
+ /**
190
+ * Call AI provider based on agent configuration
191
+ * @param {Object} agent - Agent with providerId and credentials
192
+ * @param {string} prompt - User prompt
193
+ * @param {string} systemPrompt - System prompt
194
+ * @returns {Promise<string|null>} AI response or null on error
195
+ */
196
+ const callAI = async (agent, prompt, systemPrompt = '') => {
197
+ if (!agent || !agent.providerId) return null;
198
+
199
+ switch (agent.providerId) {
200
+ case 'anthropic':
201
+ return callAnthropic(agent, prompt, systemPrompt);
202
+
203
+ case 'gemini':
204
+ return callGemini(agent, prompt, systemPrompt);
205
+
206
+ // All OpenAI-compatible APIs
207
+ case 'openai':
208
+ case 'openrouter':
209
+ case 'deepseek':
210
+ case 'groq':
211
+ case 'xai':
212
+ case 'mistral':
213
+ case 'perplexity':
214
+ case 'together':
215
+ case 'qwen':
216
+ case 'moonshot':
217
+ case 'yi':
218
+ case 'zhipu':
219
+ case 'baichuan':
220
+ case 'ollama':
221
+ case 'lmstudio':
222
+ case 'custom':
223
+ return callOpenAICompatible(agent, prompt, systemPrompt);
224
+
225
+ default:
226
+ return null;
227
+ }
228
+ };
229
+
230
+ /**
231
+ * Analyze trading data with AI
232
+ * @param {Object} agent - AI agent
233
+ * @param {Object} data - Trading data from APIs
234
+ * @returns {Promise<Object|null>} Analysis result or null
235
+ */
236
+ const analyzeTrading = async (agent, data) => {
237
+ if (!agent || !data) return null;
238
+
239
+ const systemPrompt = `You are a professional trading analyst for prop firm futures trading.
240
+ Analyze the provided real-time trading data and provide actionable insights.
241
+ Be concise. Focus on risk management and optimization.
242
+ Respond in JSON format with: { "action": "HOLD|REDUCE_SIZE|PAUSE|CONTINUE", "confidence": 0-100, "reason": "brief reason" }`;
243
+
244
+ const prompt = `Current trading session data:
245
+ - Account Balance: ${data.account?.balance ?? 'N/A'}
246
+ - Today P&L: ${data.account?.profitAndLoss ?? 'N/A'}
247
+ - Open Positions: ${data.positions?.length ?? 0}
248
+ - Open Orders: ${data.orders?.length ?? 0}
249
+ - Today Trades: ${data.trades?.length ?? 0}
250
+
251
+ ${data.positions?.length > 0 ? `Positions: ${JSON.stringify(data.positions.map(p => ({
252
+ symbol: p.symbol || p.contractId,
253
+ qty: p.quantity,
254
+ pnl: p.profitAndLoss
255
+ })))}` : ''}
256
+
257
+ Analyze and provide recommendation.`;
258
+
259
+ try {
260
+ const response = await callAI(agent, prompt, systemPrompt);
261
+ if (!response) return null;
262
+
263
+ // Try to parse JSON from response
264
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
265
+ if (jsonMatch) {
266
+ return JSON.parse(jsonMatch[0]);
267
+ }
268
+
269
+ return null;
270
+ } catch (error) {
271
+ return null;
272
+ }
273
+ };
274
+
275
+ module.exports = {
276
+ callAI,
277
+ analyzeTrading,
278
+ callOpenAICompatible,
279
+ callAnthropic,
280
+ callGemini
281
+ };
@@ -214,36 +214,9 @@ const addAgent = async (providerId, optionId, credentials, model = null, customN
214
214
 
215
215
  saveAISettings(aiSettings);
216
216
 
217
- // Start AI supervision automatically
217
+ // Agent is ready - supervision will start when algo trading begins
218
+ // Supervision requires a real service connection and account
218
219
  const agent = getAgent(agentId);
219
- if (agent) {
220
- // Mock algo target - in real implementation, this would be HQX Ultra Scalping
221
- const mockAlgo = {
222
- name: 'HQX Ultra Scalping',
223
- status: 'ready',
224
- active: false
225
- };
226
-
227
- // Check if other agents are already supervising
228
- const allAgents = getAgents();
229
- const activeSupervisionCount = allAgents.filter(a => a.id !== agentId && a.isActive).length;
230
-
231
- if (activeSupervisionCount === 0) {
232
- // First agent - start single supervision
233
- AISupervisor.start(agentId, mockAlgo);
234
- console.log(`\nšŸ¤– AI supervision started for ${agent.name}`);
235
- console.log(` Monitoring: HQX Ultra Scalping`);
236
- console.log(` Mode: Single agent supervision`);
237
- } else {
238
- // Additional agent - switch to consensus mode
239
- console.log(`\nšŸ¤– ${agent.name} added to supervision`);
240
- console.log(` Switching to multi-agent consensus mode`);
241
- console.log(` Total agents: ${allAgents.length}`);
242
-
243
- // Start consensus mode would be handled by supervisor
244
- AISupervisor.start(agentId, mockAlgo);
245
- }
246
- }
247
220
 
248
221
  return agent;
249
222
  };