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/ai-models.js
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Models - Fetch from provider APIs
|
|
3
|
-
*
|
|
4
|
-
* Models are fetched dynamically from each provider's API.
|
|
5
|
-
* Exception: MiniMax (no /models API) - see RULES.md for details.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const https = require('https');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* API endpoints for fetching models
|
|
12
|
-
* null = provider doesn't have /models endpoint
|
|
13
|
-
*/
|
|
14
|
-
const API_ENDPOINTS = {
|
|
15
|
-
anthropic: 'https://api.anthropic.com/v1/models',
|
|
16
|
-
openai: 'https://api.openai.com/v1/models',
|
|
17
|
-
google: 'https://generativelanguage.googleapis.com/v1beta/models',
|
|
18
|
-
minimax: null, // No /models API - uses MINIMAX_MODELS (see RULES.md exception)
|
|
19
|
-
deepseek: 'https://api.deepseek.com/v1/models',
|
|
20
|
-
mistral: 'https://api.mistral.ai/v1/models',
|
|
21
|
-
xai: 'https://api.x.ai/v1/models',
|
|
22
|
-
openrouter: 'https://openrouter.ai/api/v1/models',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* MiniMax Models - EXCEPTION to no-hardcode rule (see RULES.md)
|
|
27
|
-
*
|
|
28
|
-
* MiniMax does not provide /models API endpoint.
|
|
29
|
-
* Confirmed by: OpenCode, Cursor, LiteLLM - all use hardcoded models.
|
|
30
|
-
* Source: https://platform.minimax.io/docs/api-reference/text-intro
|
|
31
|
-
*/
|
|
32
|
-
const MINIMAX_MODELS = [
|
|
33
|
-
{ id: 'MiniMax-M2.1', name: 'MiniMax-M2.1' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Make HTTPS request
|
|
38
|
-
* @param {string} url - API URL
|
|
39
|
-
* @param {Object} headers - Request headers
|
|
40
|
-
* @param {number} timeout - Timeout in ms (default 60000 per RULES.md #15)
|
|
41
|
-
* @returns {Promise<Object>} Response data
|
|
42
|
-
*/
|
|
43
|
-
const fetchApi = (url, headers = {}, timeout = 60000) => {
|
|
44
|
-
return new Promise((resolve, reject) => {
|
|
45
|
-
const urlObj = new URL(url);
|
|
46
|
-
const options = {
|
|
47
|
-
hostname: urlObj.hostname,
|
|
48
|
-
path: urlObj.pathname + urlObj.search,
|
|
49
|
-
method: 'GET',
|
|
50
|
-
headers: {
|
|
51
|
-
'Content-Type': 'application/json',
|
|
52
|
-
...headers
|
|
53
|
-
},
|
|
54
|
-
timeout
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const req = https.request(options, (res) => {
|
|
58
|
-
let data = '';
|
|
59
|
-
res.on('data', chunk => data += chunk);
|
|
60
|
-
res.on('end', () => {
|
|
61
|
-
try {
|
|
62
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
63
|
-
resolve({ success: true, data: JSON.parse(data) });
|
|
64
|
-
} else {
|
|
65
|
-
resolve({ success: false, error: `HTTP ${res.statusCode}` });
|
|
66
|
-
}
|
|
67
|
-
} catch (error) {
|
|
68
|
-
resolve({ success: false, error: 'Invalid JSON response' });
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
req.on('error', (error) => {
|
|
74
|
-
resolve({ success: false, error: error.message });
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
req.on('timeout', () => {
|
|
78
|
-
req.destroy();
|
|
79
|
-
resolve({ success: false, error: 'Request timeout' });
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
req.end();
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Get auth headers for provider
|
|
88
|
-
* @param {string} providerId - Provider ID
|
|
89
|
-
* @param {string} apiKey - API key
|
|
90
|
-
* @returns {Object} Headers object
|
|
91
|
-
*/
|
|
92
|
-
const getAuthHeaders = (providerId, apiKey) => {
|
|
93
|
-
if (!apiKey) return {};
|
|
94
|
-
|
|
95
|
-
switch (providerId) {
|
|
96
|
-
case 'anthropic':
|
|
97
|
-
return { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' };
|
|
98
|
-
case 'openai':
|
|
99
|
-
case 'deepseek':
|
|
100
|
-
case 'minimax':
|
|
101
|
-
case 'xai':
|
|
102
|
-
case 'openrouter':
|
|
103
|
-
case 'mistral':
|
|
104
|
-
return { 'Authorization': `Bearer ${apiKey}` };
|
|
105
|
-
case 'google':
|
|
106
|
-
return {}; // Google uses query param
|
|
107
|
-
default:
|
|
108
|
-
return { 'Authorization': `Bearer ${apiKey}` };
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Excluded patterns - models NOT suitable for algo trading
|
|
114
|
-
* These are image, audio, embedding, moderation models
|
|
115
|
-
*/
|
|
116
|
-
const EXCLUDED_PATTERNS = [
|
|
117
|
-
'whisper', 'tts', 'dall-e', 'embedding', 'embed', 'moderation',
|
|
118
|
-
'image', 'vision', 'audio', 'speech', 'realtime', 'transcription',
|
|
119
|
-
'aqa', 'gecko', 'bison', 'learnlm'
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check if model should be excluded (not for algo trading)
|
|
124
|
-
* @param {string} modelId - Model ID
|
|
125
|
-
* @returns {boolean} True if should be excluded
|
|
126
|
-
*/
|
|
127
|
-
const shouldExcludeModel = (modelId) => {
|
|
128
|
-
const id = modelId.toLowerCase();
|
|
129
|
-
return EXCLUDED_PATTERNS.some(pattern => id.includes(pattern));
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Extract version number from model ID for sorting
|
|
134
|
-
* @param {string} modelId - Model ID
|
|
135
|
-
* @returns {number} Version number (higher = newer)
|
|
136
|
-
*/
|
|
137
|
-
const extractVersion = (modelId) => {
|
|
138
|
-
const id = modelId.toLowerCase();
|
|
139
|
-
|
|
140
|
-
// Gemini: gemini-3 > gemini-2.5 > gemini-2.0
|
|
141
|
-
const geminiMatch = id.match(/gemini-(\d+\.?\d*)/);
|
|
142
|
-
if (geminiMatch) return parseFloat(geminiMatch[1]) * 100;
|
|
143
|
-
|
|
144
|
-
// Claude: opus-4.5 > opus-4 > sonnet-4 > haiku
|
|
145
|
-
if (id.includes('opus-4.5') || id.includes('opus-4-5')) return 450;
|
|
146
|
-
if (id.includes('opus-4.1') || id.includes('opus-4-1')) return 410;
|
|
147
|
-
if (id.includes('opus-4')) return 400;
|
|
148
|
-
if (id.includes('sonnet-4.5') || id.includes('sonnet-4-5')) return 350;
|
|
149
|
-
if (id.includes('sonnet-4')) return 340;
|
|
150
|
-
if (id.includes('haiku-4.5') || id.includes('haiku-4-5')) return 250;
|
|
151
|
-
if (id.includes('sonnet-3.7') || id.includes('3-7-sonnet')) return 237;
|
|
152
|
-
if (id.includes('sonnet-3.5') || id.includes('3-5-sonnet')) return 235;
|
|
153
|
-
if (id.includes('haiku-3.5') || id.includes('3-5-haiku')) return 135;
|
|
154
|
-
if (id.includes('opus')) return 300;
|
|
155
|
-
if (id.includes('sonnet')) return 200;
|
|
156
|
-
if (id.includes('haiku')) return 100;
|
|
157
|
-
|
|
158
|
-
// GPT: gpt-4o > gpt-4-turbo > gpt-4 > gpt-3.5
|
|
159
|
-
if (id.includes('gpt-4o')) return 450;
|
|
160
|
-
if (id.includes('gpt-4-turbo')) return 420;
|
|
161
|
-
if (id.includes('gpt-4')) return 400;
|
|
162
|
-
if (id.includes('gpt-3.5')) return 350;
|
|
163
|
-
if (id.includes('o1')) return 500; // o1 reasoning models
|
|
164
|
-
if (id.includes('o3')) return 530; // o3 reasoning models
|
|
165
|
-
|
|
166
|
-
// Mistral: large > medium > small
|
|
167
|
-
if (id.includes('large')) return 300;
|
|
168
|
-
if (id.includes('medium')) return 200;
|
|
169
|
-
if (id.includes('small') || id.includes('tiny')) return 100;
|
|
170
|
-
|
|
171
|
-
// Default
|
|
172
|
-
return 50;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Get model tier for display (Pro/Flash/Lite)
|
|
177
|
-
* @param {string} modelId - Model ID
|
|
178
|
-
* @returns {number} Tier weight (higher = more powerful)
|
|
179
|
-
*/
|
|
180
|
-
const getModelTier = (modelId) => {
|
|
181
|
-
const id = modelId.toLowerCase();
|
|
182
|
-
if (id.includes('pro') || id.includes('opus') || id.includes('large')) return 30;
|
|
183
|
-
if (id.includes('flash') || id.includes('sonnet') || id.includes('medium')) return 20;
|
|
184
|
-
if (id.includes('lite') || id.includes('haiku') || id.includes('small')) return 10;
|
|
185
|
-
return 15;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Parse models response based on provider - filtered for algo trading
|
|
190
|
-
* @param {string} providerId - Provider ID
|
|
191
|
-
* @param {Object} data - API response data
|
|
192
|
-
* @returns {Array} Parsed and filtered models list
|
|
193
|
-
*/
|
|
194
|
-
const parseModelsResponse = (providerId, data) => {
|
|
195
|
-
if (!data) return [];
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
let models = [];
|
|
199
|
-
|
|
200
|
-
switch (providerId) {
|
|
201
|
-
case 'anthropic':
|
|
202
|
-
// Anthropic returns { data: [{ id, display_name, ... }] }
|
|
203
|
-
models = (data.data || [])
|
|
204
|
-
.filter(m => m.id && !shouldExcludeModel(m.id))
|
|
205
|
-
.map(m => ({
|
|
206
|
-
id: m.id,
|
|
207
|
-
name: m.display_name || m.id
|
|
208
|
-
}));
|
|
209
|
-
break;
|
|
210
|
-
|
|
211
|
-
case 'openai':
|
|
212
|
-
// OpenAI format: { data: [{ id, ... }] }
|
|
213
|
-
models = (data.data || [])
|
|
214
|
-
.filter(m => m.id && !shouldExcludeModel(m.id))
|
|
215
|
-
.filter(m => m.id.startsWith('gpt-') || m.id.startsWith('o1') || m.id.startsWith('o3'))
|
|
216
|
-
.map(m => ({
|
|
217
|
-
id: m.id,
|
|
218
|
-
name: m.id
|
|
219
|
-
}));
|
|
220
|
-
break;
|
|
221
|
-
|
|
222
|
-
case 'google':
|
|
223
|
-
// Google format: { models: [{ name, displayName, supportedGenerationMethods }] }
|
|
224
|
-
models = (data.models || [])
|
|
225
|
-
.filter(m => {
|
|
226
|
-
const id = m.name?.replace('models/', '') || '';
|
|
227
|
-
// Only Gemini chat models
|
|
228
|
-
return id.startsWith('gemini-') &&
|
|
229
|
-
!shouldExcludeModel(id) &&
|
|
230
|
-
m.supportedGenerationMethods?.includes('generateContent');
|
|
231
|
-
})
|
|
232
|
-
.map(m => ({
|
|
233
|
-
id: m.name?.replace('models/', '') || m.name,
|
|
234
|
-
name: m.displayName || m.name
|
|
235
|
-
}));
|
|
236
|
-
break;
|
|
237
|
-
|
|
238
|
-
case 'deepseek':
|
|
239
|
-
// DeepSeek format: { data: [{ id, ... }] } - OpenAI compatible
|
|
240
|
-
models = (data.data || [])
|
|
241
|
-
.filter(m => m.id && !shouldExcludeModel(m.id))
|
|
242
|
-
.filter(m => m.id.includes('deepseek'))
|
|
243
|
-
.map(m => ({
|
|
244
|
-
id: m.id,
|
|
245
|
-
name: m.id
|
|
246
|
-
}));
|
|
247
|
-
break;
|
|
248
|
-
|
|
249
|
-
case 'minimax':
|
|
250
|
-
// MiniMax format: { data: [{ id, ... }] } or { models: [...] }
|
|
251
|
-
models = (data.data || data.models || [])
|
|
252
|
-
.filter(m => (m.id || m.model) && !shouldExcludeModel(m.id || m.model))
|
|
253
|
-
.map(m => ({
|
|
254
|
-
id: m.id || m.model,
|
|
255
|
-
name: m.id || m.model
|
|
256
|
-
}));
|
|
257
|
-
break;
|
|
258
|
-
|
|
259
|
-
case 'xai':
|
|
260
|
-
// xAI format: { data: [{ id, ... }] }
|
|
261
|
-
models = (data.data || [])
|
|
262
|
-
.filter(m => m.id && !shouldExcludeModel(m.id))
|
|
263
|
-
.filter(m => m.id.includes('grok'))
|
|
264
|
-
.map(m => ({
|
|
265
|
-
id: m.id,
|
|
266
|
-
name: m.id
|
|
267
|
-
}));
|
|
268
|
-
break;
|
|
269
|
-
|
|
270
|
-
case 'mistral':
|
|
271
|
-
// Mistral format: { data: [{ id, ... }] }
|
|
272
|
-
models = (data.data || [])
|
|
273
|
-
.filter(m => m.id && !shouldExcludeModel(m.id))
|
|
274
|
-
.map(m => ({
|
|
275
|
-
id: m.id,
|
|
276
|
-
name: m.id
|
|
277
|
-
}));
|
|
278
|
-
break;
|
|
279
|
-
|
|
280
|
-
case 'perplexity':
|
|
281
|
-
// Perplexity format varies
|
|
282
|
-
models = (data.models || data.data || [])
|
|
283
|
-
.filter(m => (m.id || m.model) && !shouldExcludeModel(m.id || m.model))
|
|
284
|
-
.map(m => ({
|
|
285
|
-
id: m.id || m.model,
|
|
286
|
-
name: m.id || m.model
|
|
287
|
-
}));
|
|
288
|
-
break;
|
|
289
|
-
|
|
290
|
-
case 'openrouter':
|
|
291
|
-
// OpenRouter format: { data: [{ id, name, ... }] }
|
|
292
|
-
// Filter to show only main providers' chat models
|
|
293
|
-
models = (data.data || [])
|
|
294
|
-
.filter(m => {
|
|
295
|
-
if (!m.id || shouldExcludeModel(m.id)) return false;
|
|
296
|
-
// Only keep major providers for trading
|
|
297
|
-
const validPrefixes = [
|
|
298
|
-
'anthropic/claude', 'openai/gpt', 'openai/o1', 'openai/o3',
|
|
299
|
-
'google/gemini', 'mistralai/', 'meta-llama/', 'x-ai/grok'
|
|
300
|
-
];
|
|
301
|
-
return validPrefixes.some(p => m.id.startsWith(p));
|
|
302
|
-
})
|
|
303
|
-
.map(m => ({
|
|
304
|
-
id: m.id,
|
|
305
|
-
name: m.name || m.id
|
|
306
|
-
}));
|
|
307
|
-
break;
|
|
308
|
-
|
|
309
|
-
default:
|
|
310
|
-
return [];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Sort by version (newest first), then by tier (most powerful first)
|
|
314
|
-
return models.sort((a, b) => {
|
|
315
|
-
const versionDiff = extractVersion(b.id) - extractVersion(a.id);
|
|
316
|
-
if (versionDiff !== 0) return versionDiff;
|
|
317
|
-
return getModelTier(b.id) - getModelTier(a.id);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
} catch (error) {
|
|
321
|
-
return [];
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Fetch models from provider API
|
|
327
|
-
* @param {string} providerId - Provider ID
|
|
328
|
-
* @param {string} apiKey - API key (required for most providers)
|
|
329
|
-
* @returns {Promise<Object>} { success, models, error }
|
|
330
|
-
*/
|
|
331
|
-
const fetchModelsFromApi = async (providerId, apiKey) => {
|
|
332
|
-
// MiniMax: no /models API, use hardcoded list (see RULES.md exception)
|
|
333
|
-
if (providerId === 'minimax') {
|
|
334
|
-
return { success: true, models: MINIMAX_MODELS, error: null };
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const endpoint = API_ENDPOINTS[providerId];
|
|
338
|
-
|
|
339
|
-
if (!endpoint) {
|
|
340
|
-
return { success: false, models: [], error: 'Unknown provider' };
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Build URL (Google needs API key in query)
|
|
344
|
-
let url = endpoint;
|
|
345
|
-
if (providerId === 'google' && apiKey) {
|
|
346
|
-
url += `?key=${apiKey}`;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const headers = getAuthHeaders(providerId, apiKey);
|
|
350
|
-
const result = await fetchApi(url, headers);
|
|
351
|
-
|
|
352
|
-
if (!result.success) {
|
|
353
|
-
return { success: false, models: [], error: result.error };
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const models = parseModelsResponse(providerId, result.data);
|
|
357
|
-
|
|
358
|
-
if (models.length === 0) {
|
|
359
|
-
return { success: false, models: [], error: 'No models returned' };
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return { success: true, models, error: null };
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Get models for a provider - returns empty, use fetchModelsFromApi
|
|
367
|
-
* @param {string} providerId - Provider ID
|
|
368
|
-
* @returns {Array} Empty array
|
|
369
|
-
*/
|
|
370
|
-
const getModelsForProvider = (providerId) => {
|
|
371
|
-
return [];
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Get model by ID - returns null, use API data
|
|
376
|
-
* @param {string} providerId - Provider ID
|
|
377
|
-
* @param {string} modelId - Model ID
|
|
378
|
-
* @returns {null} Always null
|
|
379
|
-
*/
|
|
380
|
-
const getModelById = (providerId, modelId) => {
|
|
381
|
-
return null;
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
module.exports = {
|
|
385
|
-
fetchModelsFromApi,
|
|
386
|
-
getModelsForProvider,
|
|
387
|
-
getModelById,
|
|
388
|
-
API_ENDPOINTS
|
|
389
|
-
};
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Algo Executor - Shared execution engine for all algo modes
|
|
3
|
-
* Handles market data, signals, orders, and P&L tracking
|
|
4
|
-
* Supports multi-agent AI supervision for signal optimization
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
9
|
-
const { M1 } = require('../../lib/m/s1');
|
|
10
|
-
const { MarketDataFeed } = require('../../lib/data');
|
|
11
|
-
const { SupervisionEngine } = require('../../services/ai-supervision');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Execute algo strategy with market data
|
|
15
|
-
* @param {Object} params - Execution parameters
|
|
16
|
-
* @param {Object} params.service - Trading service (Rithmic/ProjectX)
|
|
17
|
-
* @param {Object} params.account - Account object
|
|
18
|
-
* @param {Object} params.contract - Contract object
|
|
19
|
-
* @param {Object} params.config - Algo config (contracts, target, risk, showName)
|
|
20
|
-
* @param {Object} params.options - Optional: supervisionConfig for multi-agent AI
|
|
21
|
-
*/
|
|
22
|
-
const executeAlgo = async ({ service, account, contract, config, options = {} }) => {
|
|
23
|
-
const { contracts, dailyTarget, maxRisk, showName } = config;
|
|
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;
|
|
29
|
-
|
|
30
|
-
const accountName = showName
|
|
31
|
-
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
32
|
-
: 'HQX *****';
|
|
33
|
-
const symbolName = contract.name;
|
|
34
|
-
const contractId = contract.id;
|
|
35
|
-
const tickSize = contract.tickSize || 0.25;
|
|
36
|
-
|
|
37
|
-
const ui = new AlgoUI({
|
|
38
|
-
subtitle: subtitle || (supervisionEnabled ? 'HQX + AI SUPERVISION' : 'HQX Ultra Scalping'),
|
|
39
|
-
mode: 'one-account'
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const stats = {
|
|
43
|
-
accountName,
|
|
44
|
-
symbol: symbolName,
|
|
45
|
-
qty: contracts,
|
|
46
|
-
target: dailyTarget,
|
|
47
|
-
risk: maxRisk,
|
|
48
|
-
propfirm: account.propfirm || 'Unknown',
|
|
49
|
-
platform: account.platform || 'Rithmic',
|
|
50
|
-
pnl: 0,
|
|
51
|
-
trades: 0,
|
|
52
|
-
wins: 0,
|
|
53
|
-
losses: 0,
|
|
54
|
-
latency: 0,
|
|
55
|
-
connected: false,
|
|
56
|
-
startTime: Date.now()
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
let running = true;
|
|
60
|
-
let stopReason = null;
|
|
61
|
-
let startingPnL = null;
|
|
62
|
-
let currentPosition = 0;
|
|
63
|
-
let pendingOrder = false;
|
|
64
|
-
let tickCount = 0;
|
|
65
|
-
|
|
66
|
-
// Context for AI supervision
|
|
67
|
-
const aiContext = { recentTicks: [], recentSignals: [], recentTrades: [], maxTicks: 100 };
|
|
68
|
-
|
|
69
|
-
// Initialize Strategy
|
|
70
|
-
const strategy = new M1({ tickSize });
|
|
71
|
-
strategy.initialize(contractId, tickSize);
|
|
72
|
-
|
|
73
|
-
// Initialize Market Data Feed
|
|
74
|
-
const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
|
|
75
|
-
|
|
76
|
-
// Log startup
|
|
77
|
-
ui.addLog('info', `Strategy: ${supervisionEnabled ? 'HQX + AI Supervision' : 'HQX Ultra Scalping'}`);
|
|
78
|
-
ui.addLog('info', `Account: ${accountName}`);
|
|
79
|
-
ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
|
|
80
|
-
ui.addLog('info', `Target: $${dailyTarget} | Risk: $${maxRisk}`);
|
|
81
|
-
if (supervisionEnabled) {
|
|
82
|
-
const agentCount = supervisionEngine.getActiveCount();
|
|
83
|
-
ui.addLog('info', `AI Agents: ${agentCount} active`);
|
|
84
|
-
}
|
|
85
|
-
ui.addLog('info', 'Connecting to market data...');
|
|
86
|
-
|
|
87
|
-
// Handle strategy signals
|
|
88
|
-
strategy.on('signal', async (signal) => {
|
|
89
|
-
if (!running || pendingOrder || currentPosition !== 0) return;
|
|
90
|
-
|
|
91
|
-
let { direction, entry, stopLoss, takeProfit, confidence } = signal;
|
|
92
|
-
let orderSize = contracts;
|
|
93
|
-
|
|
94
|
-
aiContext.recentSignals.push({ ...signal, timestamp: Date.now() });
|
|
95
|
-
if (aiContext.recentSignals.length > 10) aiContext.recentSignals.shift();
|
|
96
|
-
|
|
97
|
-
ui.addLog('info', `Signal: ${direction.toUpperCase()} @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
|
|
98
|
-
|
|
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}`);
|
|
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
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Place order
|
|
142
|
-
pendingOrder = true;
|
|
143
|
-
try {
|
|
144
|
-
const orderSide = direction === 'long' ? 0 : 1;
|
|
145
|
-
const orderResult = await service.placeOrder({
|
|
146
|
-
accountId: account.accountId,
|
|
147
|
-
contractId: contractId,
|
|
148
|
-
type: 2,
|
|
149
|
-
side: orderSide,
|
|
150
|
-
size: orderSize
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (orderResult.success) {
|
|
154
|
-
currentPosition = direction === 'long' ? orderSize : -orderSize;
|
|
155
|
-
stats.trades++;
|
|
156
|
-
ui.addLog('fill_' + (direction === 'long' ? 'buy' : 'sell'),
|
|
157
|
-
`OPENED ${direction.toUpperCase()} ${orderSize}x @ market`);
|
|
158
|
-
|
|
159
|
-
// Bracket orders
|
|
160
|
-
if (stopLoss && takeProfit) {
|
|
161
|
-
await service.placeOrder({
|
|
162
|
-
accountId: account.accountId, contractId, type: 4,
|
|
163
|
-
side: direction === 'long' ? 1 : 0, size: orderSize, stopPrice: stopLoss
|
|
164
|
-
});
|
|
165
|
-
await service.placeOrder({
|
|
166
|
-
accountId: account.accountId, contractId, type: 1,
|
|
167
|
-
side: direction === 'long' ? 1 : 0, size: orderSize, limitPrice: takeProfit
|
|
168
|
-
});
|
|
169
|
-
ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
ui.addLog('error', `Order failed: ${orderResult.error}`);
|
|
173
|
-
}
|
|
174
|
-
} catch (e) {
|
|
175
|
-
ui.addLog('error', `Order error: ${e.message}`);
|
|
176
|
-
}
|
|
177
|
-
pendingOrder = false;
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// Handle market data ticks
|
|
181
|
-
marketFeed.on('tick', (tick) => {
|
|
182
|
-
tickCount++;
|
|
183
|
-
const latencyStart = Date.now();
|
|
184
|
-
|
|
185
|
-
aiContext.recentTicks.push(tick);
|
|
186
|
-
if (aiContext.recentTicks.length > aiContext.maxTicks) aiContext.recentTicks.shift();
|
|
187
|
-
|
|
188
|
-
strategy.processTick({
|
|
189
|
-
contractId: tick.contractId || contractId,
|
|
190
|
-
price: tick.price, bid: tick.bid, ask: tick.ask,
|
|
191
|
-
volume: tick.volume || 1, side: tick.lastTradeSide || 'unknown',
|
|
192
|
-
timestamp: tick.timestamp || Date.now()
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
stats.latency = Date.now() - latencyStart;
|
|
196
|
-
if (tickCount % 100 === 0) ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
marketFeed.on('connected', () => { stats.connected = true; ui.addLog('connected', 'Market data connected!'); });
|
|
200
|
-
marketFeed.on('error', (err) => ui.addLog('error', `Market: ${err.message}`));
|
|
201
|
-
marketFeed.on('disconnected', () => { stats.connected = false; ui.addLog('error', 'Market disconnected'); });
|
|
202
|
-
|
|
203
|
-
// Connect to market data
|
|
204
|
-
try {
|
|
205
|
-
const token = service.token || service.getToken?.();
|
|
206
|
-
const propfirmKey = (account.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
|
|
207
|
-
await marketFeed.connect(token, propfirmKey, contractId);
|
|
208
|
-
await marketFeed.subscribe(symbolName, contractId);
|
|
209
|
-
} catch (e) {
|
|
210
|
-
ui.addLog('error', `Failed to connect: ${e.message}`);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Poll P&L
|
|
214
|
-
const pollPnL = async () => {
|
|
215
|
-
try {
|
|
216
|
-
const accountResult = await service.getTradingAccounts();
|
|
217
|
-
if (accountResult.success && accountResult.accounts) {
|
|
218
|
-
const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
|
|
219
|
-
if (acc && acc.profitAndLoss !== undefined) {
|
|
220
|
-
if (startingPnL === null) startingPnL = acc.profitAndLoss;
|
|
221
|
-
stats.pnl = acc.profitAndLoss - startingPnL;
|
|
222
|
-
if (stats.pnl !== 0) strategy.recordTradeResult(stats.pnl);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const posResult = await service.getPositions(account.accountId);
|
|
227
|
-
if (posResult.success && posResult.positions) {
|
|
228
|
-
const pos = posResult.positions.find(p => {
|
|
229
|
-
const sym = p.contractId || p.symbol || '';
|
|
230
|
-
return sym.includes(contract.name) || sym.includes(contractId);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
if (pos && pos.quantity !== 0) {
|
|
234
|
-
currentPosition = pos.quantity;
|
|
235
|
-
const pnl = pos.profitAndLoss || 0;
|
|
236
|
-
if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
|
|
237
|
-
else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
|
|
238
|
-
} else {
|
|
239
|
-
currentPosition = 0;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (stats.pnl >= dailyTarget) {
|
|
244
|
-
stopReason = 'target'; running = false;
|
|
245
|
-
ui.addLog('fill_win', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
|
|
246
|
-
} else if (stats.pnl <= -maxRisk) {
|
|
247
|
-
stopReason = 'risk'; running = false;
|
|
248
|
-
ui.addLog('fill_loss', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
249
|
-
}
|
|
250
|
-
} catch (e) { /* silent */ }
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
// Start loops
|
|
254
|
-
const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
|
|
255
|
-
const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
|
|
256
|
-
pollPnL();
|
|
257
|
-
|
|
258
|
-
// Keyboard handler
|
|
259
|
-
const setupKeyHandler = () => {
|
|
260
|
-
if (!process.stdin.isTTY) return;
|
|
261
|
-
readline.emitKeypressEvents(process.stdin);
|
|
262
|
-
process.stdin.setRawMode(true);
|
|
263
|
-
process.stdin.resume();
|
|
264
|
-
|
|
265
|
-
const onKey = (str, key) => {
|
|
266
|
-
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
267
|
-
running = false; stopReason = 'manual';
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
process.stdin.on('keypress', onKey);
|
|
271
|
-
return () => {
|
|
272
|
-
process.stdin.removeListener('keypress', onKey);
|
|
273
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
274
|
-
};
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
const cleanupKeys = setupKeyHandler();
|
|
278
|
-
|
|
279
|
-
// Wait for stop
|
|
280
|
-
await new Promise(resolve => {
|
|
281
|
-
const check = setInterval(() => { if (!running) { clearInterval(check); resolve(); } }, 100);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// Cleanup
|
|
285
|
-
clearInterval(refreshInterval);
|
|
286
|
-
clearInterval(pnlInterval);
|
|
287
|
-
await marketFeed.disconnect();
|
|
288
|
-
if (cleanupKeys) cleanupKeys();
|
|
289
|
-
ui.cleanup();
|
|
290
|
-
|
|
291
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
292
|
-
process.stdin.resume();
|
|
293
|
-
|
|
294
|
-
// Duration
|
|
295
|
-
const durationMs = Date.now() - stats.startTime;
|
|
296
|
-
const hours = Math.floor(durationMs / 3600000);
|
|
297
|
-
const minutes = Math.floor((durationMs % 3600000) / 60000);
|
|
298
|
-
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
299
|
-
stats.duration = hours > 0 ? `${hours}h ${minutes}m ${seconds}s` : minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
300
|
-
|
|
301
|
-
renderSessionSummary(stats, stopReason);
|
|
302
|
-
|
|
303
|
-
console.log('\n Returning to menu in 3 seconds...');
|
|
304
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
module.exports = { executeAlgo };
|