hedgequantx 2.6.158 → 2.6.159

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.6.158",
3
+ "version": "2.6.159",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Algo Trading Configuration
3
+ * Symbol selection and algo parameters configuration
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const chalk = require('chalk');
9
+ const ora = require('ora');
10
+ const { prompts } = require('../../utils');
11
+ const aiService = require('../../services/ai');
12
+ const { MAX_MULTI_SYMBOLS } = require('./algo-utils');
13
+
14
+ /**
15
+ * Symbol selection - sorted with popular indices first
16
+ * Supports selecting multiple symbols (up to MAX_MULTI_SYMBOLS)
17
+ * @param {Object} service - Trading service
18
+ * @param {Object} account - Trading account
19
+ * @param {boolean} allowMultiple - Allow selecting multiple symbols
20
+ * @returns {Object|Array|null} Single contract or array of contracts
21
+ */
22
+ const selectSymbol = async (service, account, allowMultiple = false) => {
23
+ const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
24
+
25
+ const contractsResult = await service.getContracts();
26
+ if (!contractsResult.success || !contractsResult.contracts?.length) {
27
+ spinner.fail('FAILED TO LOAD CONTRACTS');
28
+ return null;
29
+ }
30
+
31
+ let contracts = contractsResult.contracts;
32
+
33
+ // Sort: Popular indices first (ES, NQ, MES, MNQ, RTY, YM, etc.)
34
+ const popularPrefixes = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
35
+
36
+ contracts.sort((a, b) => {
37
+ const nameA = a.name || '';
38
+ const nameB = b.name || '';
39
+
40
+ const idxA = popularPrefixes.findIndex(p => nameA.startsWith(p));
41
+ const idxB = popularPrefixes.findIndex(p => nameB.startsWith(p));
42
+
43
+ if (idxA !== -1 && idxB !== -1) return idxA - idxB;
44
+ if (idxA !== -1) return -1;
45
+ if (idxB !== -1) return 1;
46
+ return nameA.localeCompare(nameB);
47
+ });
48
+
49
+ spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
50
+
51
+ const options = contracts.map(c => {
52
+ const name = c.name || c.symbol || c.baseSymbol;
53
+ const desc = c.description || '';
54
+ const label = desc ? `${name} - ${desc}` : name;
55
+ return { label, value: c };
56
+ });
57
+
58
+ options.push({ label: chalk.gray('< BACK'), value: 'back' });
59
+
60
+ // Single symbol mode
61
+ if (!allowMultiple) {
62
+ const contract = await prompts.selectOption(chalk.yellow('SELECT SYMBOL:'), options);
63
+ return contract === 'back' || contract === null ? null : contract;
64
+ }
65
+
66
+ // Multi-symbol selection mode
67
+ const selectedContracts = [];
68
+
69
+ while (selectedContracts.length < MAX_MULTI_SYMBOLS) {
70
+ console.log();
71
+
72
+ if (selectedContracts.length > 0) {
73
+ console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_MULTI_SYMBOLS}):`));
74
+ selectedContracts.forEach((c, i) => {
75
+ const name = c.name || c.symbol;
76
+ console.log(chalk.green(` ${i + 1}. ${name} x${c.qty}`));
77
+ });
78
+ console.log();
79
+ }
80
+
81
+ const availableOptions = options.filter(opt => {
82
+ if (opt.value === 'back') return true;
83
+ const optId = opt.value.id || opt.value.symbol || opt.value.name;
84
+ return !selectedContracts.some(sc => (sc.id || sc.symbol || sc.name) === optId);
85
+ });
86
+
87
+ if (selectedContracts.length > 0) {
88
+ availableOptions.unshift({
89
+ label: chalk.green(`✓ DONE - Start trading ${selectedContracts.length} symbol${selectedContracts.length > 1 ? 's' : ''}`),
90
+ value: 'done'
91
+ });
92
+ }
93
+
94
+ const promptText = selectedContracts.length === 0
95
+ ? chalk.yellow(`SELECT SYMBOL (1/${MAX_MULTI_SYMBOLS}):`)
96
+ : chalk.yellow(`ADD SYMBOL (${selectedContracts.length + 1}/${MAX_MULTI_SYMBOLS}) OR DONE:`);
97
+
98
+ const choice = await prompts.selectOption(promptText, availableOptions);
99
+
100
+ if (choice === null || choice === 'back') {
101
+ if (selectedContracts.length === 0) return null;
102
+ selectedContracts.pop();
103
+ continue;
104
+ }
105
+
106
+ if (choice === 'done') break;
107
+
108
+ const symbolName = choice.name || choice.symbol;
109
+ const qty = await prompts.numberInput(`CONTRACTS FOR ${symbolName}:`, 1, 1, 10);
110
+ if (qty === null) continue;
111
+
112
+ choice.qty = qty;
113
+ selectedContracts.push(choice);
114
+
115
+ if (selectedContracts.length >= MAX_MULTI_SYMBOLS) {
116
+ console.log(chalk.yellow(` Maximum ${MAX_MULTI_SYMBOLS} symbols reached`));
117
+ break;
118
+ }
119
+ }
120
+
121
+ return selectedContracts.length > 0 ? selectedContracts : null;
122
+ };
123
+
124
+ /**
125
+ * Configure algo parameters
126
+ * @param {Object} account - Trading account
127
+ * @param {Object|Array} contractOrContracts - Single contract or array of contracts
128
+ */
129
+ const configureAlgo = async (account, contractOrContracts) => {
130
+ const contractList = Array.isArray(contractOrContracts) ? contractOrContracts : [contractOrContracts];
131
+ const isMultiSymbol = contractList.length > 1;
132
+
133
+ console.log();
134
+ console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
135
+
136
+ if (isMultiSymbol) {
137
+ console.log(chalk.white(` Trading ${contractList.length} symbols:`));
138
+ contractList.forEach((c, i) => {
139
+ const name = c.name || c.symbol;
140
+ const qty = c.qty || 1;
141
+ console.log(chalk.yellow(` ${i + 1}. ${name} x${qty}`));
142
+ });
143
+ }
144
+ console.log();
145
+
146
+ let contracts = 1;
147
+ if (!isMultiSymbol) {
148
+ contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
149
+ if (contracts === null) return null;
150
+ }
151
+
152
+ const dailyTarget = await prompts.numberInput('DAILY TARGET ($):', 1000, 1, 10000);
153
+ if (dailyTarget === null) return null;
154
+
155
+ const maxRisk = await prompts.numberInput('MAX RISK ($):', 500, 1, 5000);
156
+ if (maxRisk === null) return null;
157
+
158
+ const showName = await prompts.confirmPrompt('SHOW ACCOUNT NAME?', false);
159
+ if (showName === null) return null;
160
+
161
+ const aiAgents = aiService.getAgents();
162
+ let enableAI = false;
163
+
164
+ if (aiAgents.length > 0) {
165
+ console.log();
166
+ console.log(chalk.magenta(` ${aiAgents.length} AI AGENT(S) AVAILABLE:`));
167
+ aiAgents.forEach((agent, i) => {
168
+ const modelInfo = agent.model ? chalk.gray(` (${agent.model})`) : '';
169
+ console.log(chalk.white(` ${i + 1}. ${agent.name}${modelInfo}`));
170
+ });
171
+ console.log();
172
+
173
+ enableAI = await prompts.confirmPrompt('ACTIVATE AI MODELS?', true);
174
+ if (enableAI === null) return null;
175
+
176
+ if (enableAI) {
177
+ const mode = aiAgents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
178
+ console.log(chalk.green(` AI MODE: ${mode} (${aiAgents.length} agent${aiAgents.length > 1 ? 's' : ''})`));
179
+ } else {
180
+ console.log(chalk.gray(' AI AGENTS DISABLED FOR THIS SESSION'));
181
+ }
182
+ }
183
+
184
+ console.log();
185
+ const confirm = await prompts.confirmPrompt('START ALGO TRADING?', true);
186
+ if (!confirm) return null;
187
+
188
+ const initSpinner = ora({ text: 'INITIALIZING ALGO TRADING...', color: 'yellow' }).start();
189
+ await new Promise(r => setTimeout(r, 500));
190
+ initSpinner.succeed('LAUNCHING ALGO...');
191
+
192
+ return { contracts, dailyTarget, maxRisk, showName, enableAI };
193
+ };
194
+
195
+ module.exports = { selectSymbol, configureAlgo };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Algo Trading Utilities
3
+ * Shared functions and constants for algo trading
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /**
9
+ * Format price to avoid floating point errors
10
+ * @param {number} price - Raw price
11
+ * @param {number} tickSize - Tick size (default 0.25)
12
+ * @returns {string} - Formatted price string
13
+ */
14
+ const formatPrice = (price, tickSize = 0.25) => {
15
+ if (price === null || price === undefined || isNaN(price)) return '--';
16
+ const rounded = Math.round(price / tickSize) * tickSize;
17
+ const decimals = tickSize < 1 ? Math.max(0, -Math.floor(Math.log10(tickSize))) : 0;
18
+ return rounded.toFixed(decimals);
19
+ };
20
+
21
+ /**
22
+ * Check if service supports fast path (Rithmic direct)
23
+ * @param {Object} service - Trading service
24
+ * @returns {boolean}
25
+ */
26
+ const isRithmicFastPath = (service) => {
27
+ return typeof service.fastEntry === 'function' &&
28
+ typeof service.fastExit === 'function' &&
29
+ service.orderConn?.isConnected;
30
+ };
31
+
32
+ // Maximum symbols for multi-symbol trading
33
+ const MAX_MULTI_SYMBOLS = 5;
34
+
35
+ // Use HFT tick-based strategy for Rithmic
36
+ const USE_HFT_STRATEGY = true;
37
+
38
+ // Timeout for async operations
39
+ const TIMEOUT_MS = 5000;
40
+
41
+ /**
42
+ * Wrap promise with timeout
43
+ */
44
+ const withTimeout = (promise, ms) => {
45
+ return Promise.race([
46
+ promise,
47
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms))
48
+ ]);
49
+ };
50
+
51
+ module.exports = {
52
+ formatPrice,
53
+ isRithmicFastPath,
54
+ MAX_MULTI_SYMBOLS,
55
+ USE_HFT_STRATEGY,
56
+ TIMEOUT_MS,
57
+ withTimeout
58
+ };
@@ -25,39 +25,13 @@ const { algoLogger } = require('./logger');
25
25
  const { recoveryMath } = require('../../services/strategy/recovery-math');
26
26
  const { sessionHistory, SessionHistory } = require('../../services/session-history');
27
27
 
28
- // Use HFT tick-based strategy for Rithmic (fast path), M1 for ProjectX
29
- const USE_HFT_STRATEGY = true;
30
-
31
28
  // AI Strategy Supervisor - observes, learns, and optimizes the strategy
32
29
  const aiService = require('../../services/ai');
33
30
  const StrategySupervisor = require('../../services/ai/strategy-supervisor');
34
31
 
35
- /**
36
- * Format price to avoid floating point errors
37
- * Uses the tick size to round properly
38
- * @param {number} price - Raw price
39
- * @param {number} tickSize - Tick size (default 0.25)
40
- * @returns {string} - Formatted price string
41
- */
42
- const formatPrice = (price, tickSize = 0.25) => {
43
- if (price === null || price === undefined || isNaN(price)) return '--';
44
- // Round to nearest tick, then format
45
- const rounded = Math.round(price / tickSize) * tickSize;
46
- // Determine decimal places from tick size
47
- const decimals = tickSize < 1 ? Math.max(0, -Math.floor(Math.log10(tickSize))) : 0;
48
- return rounded.toFixed(decimals);
49
- };
50
-
51
- /**
52
- * Check if service supports fast path (Rithmic direct)
53
- * @param {Object} service - Trading service
54
- * @returns {boolean}
55
- */
56
- const isRithmicFastPath = (service) => {
57
- return typeof service.fastEntry === 'function' &&
58
- typeof service.fastExit === 'function' &&
59
- service.orderConn?.isConnected;
60
- };
32
+ // Shared utilities
33
+ const { formatPrice, isRithmicFastPath, MAX_MULTI_SYMBOLS, USE_HFT_STRATEGY } = require('./algo-utils');
34
+ const { selectSymbol, configureAlgo } = require('./algo-config');
61
35
 
62
36
  /**
63
37
  * One Account Menu
@@ -133,208 +107,6 @@ const oneAccountMenu = async (service) => {
133
107
  }
134
108
  };
135
109
 
136
- // Maximum symbols for multi-symbol trading
137
- const MAX_MULTI_SYMBOLS = 5;
138
-
139
- /**
140
- * Symbol selection - sorted with popular indices first
141
- * Supports selecting multiple symbols (up to MAX_MULTI_SYMBOLS)
142
- * @param {Object} service - Trading service
143
- * @param {Object} account - Trading account
144
- * @param {boolean} allowMultiple - Allow selecting multiple symbols
145
- * @returns {Object|Array|null} Single contract or array of contracts
146
- */
147
- const selectSymbol = async (service, account, allowMultiple = false) => {
148
- const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
149
-
150
- const contractsResult = await service.getContracts();
151
- if (!contractsResult.success || !contractsResult.contracts?.length) {
152
- spinner.fail('FAILED TO LOAD CONTRACTS');
153
- return null;
154
- }
155
-
156
- let contracts = contractsResult.contracts;
157
-
158
- // Sort: Popular indices first (ES, NQ, MES, MNQ, RTY, YM, etc.)
159
- const popularPrefixes = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
160
-
161
- contracts.sort((a, b) => {
162
- const nameA = a.name || '';
163
- const nameB = b.name || '';
164
-
165
- // Check if names start with popular prefixes
166
- const idxA = popularPrefixes.findIndex(p => nameA.startsWith(p));
167
- const idxB = popularPrefixes.findIndex(p => nameB.startsWith(p));
168
-
169
- // Both are popular - sort by popularity order
170
- if (idxA !== -1 && idxB !== -1) return idxA - idxB;
171
- // Only A is popular - A first
172
- if (idxA !== -1) return -1;
173
- // Only B is popular - B first
174
- if (idxB !== -1) return 1;
175
- // Neither - alphabetical
176
- return nameA.localeCompare(nameB);
177
- });
178
-
179
- spinner.succeed(`FOUND ${contracts.length} CONTRACTS`);
180
-
181
- // Display sorted contracts from API (uniform format: NAME - DESCRIPTION)
182
- const options = contracts.map(c => {
183
- const name = c.name || c.symbol || c.baseSymbol;
184
- const desc = c.description || '';
185
- const label = desc ? `${name} - ${desc}` : name;
186
- return { label, value: c };
187
- });
188
-
189
- options.push({ label: chalk.gray('< BACK'), value: 'back' });
190
-
191
- // Single symbol mode
192
- if (!allowMultiple) {
193
- const contract = await prompts.selectOption(chalk.yellow('SELECT SYMBOL:'), options);
194
- return contract === 'back' || contract === null ? null : contract;
195
- }
196
-
197
- // Multi-symbol selection mode
198
- // Each contract will have a 'qty' property for number of contracts
199
- const selectedContracts = [];
200
-
201
- while (selectedContracts.length < MAX_MULTI_SYMBOLS) {
202
- console.log();
203
-
204
- // Show already selected symbols with quantities
205
- if (selectedContracts.length > 0) {
206
- console.log(chalk.cyan(` SELECTED (${selectedContracts.length}/${MAX_MULTI_SYMBOLS}):`));
207
- selectedContracts.forEach((c, i) => {
208
- const name = c.name || c.symbol;
209
- console.log(chalk.green(` ${i + 1}. ${name} x${c.qty}`));
210
- });
211
- console.log();
212
- }
213
-
214
- // Filter out already selected
215
- const availableOptions = options.filter(opt => {
216
- if (opt.value === 'back') return true;
217
- const optId = opt.value.id || opt.value.symbol || opt.value.name;
218
- return !selectedContracts.some(sc => (sc.id || sc.symbol || sc.name) === optId);
219
- });
220
-
221
- // Add DONE option if at least 1 selected
222
- if (selectedContracts.length > 0) {
223
- availableOptions.unshift({
224
- label: chalk.green(`✓ DONE - Start trading ${selectedContracts.length} symbol${selectedContracts.length > 1 ? 's' : ''}`),
225
- value: 'done'
226
- });
227
- }
228
-
229
- const promptText = selectedContracts.length === 0
230
- ? chalk.yellow(`SELECT SYMBOL (1/${MAX_MULTI_SYMBOLS}):`)
231
- : chalk.yellow(`ADD SYMBOL (${selectedContracts.length + 1}/${MAX_MULTI_SYMBOLS}) OR DONE:`);
232
-
233
- const choice = await prompts.selectOption(promptText, availableOptions);
234
-
235
- if (choice === null || choice === 'back') {
236
- if (selectedContracts.length === 0) return null;
237
- selectedContracts.pop(); // Remove last
238
- continue;
239
- }
240
-
241
- if (choice === 'done') break;
242
-
243
- // Ask for number of contracts for this symbol
244
- const symbolName = choice.name || choice.symbol;
245
- const qty = await prompts.numberInput(`CONTRACTS FOR ${symbolName}:`, 1, 1, 10);
246
- if (qty === null) continue; // User cancelled, don't add symbol
247
-
248
- // Add qty to contract object
249
- choice.qty = qty;
250
- selectedContracts.push(choice);
251
-
252
- if (selectedContracts.length >= MAX_MULTI_SYMBOLS) {
253
- console.log(chalk.yellow(` Maximum ${MAX_MULTI_SYMBOLS} symbols reached`));
254
- break;
255
- }
256
- }
257
-
258
- return selectedContracts.length > 0 ? selectedContracts : null;
259
- };
260
-
261
- /**
262
- * Configure algo
263
- * @param {Object} account - Trading account
264
- * @param {Object|Array} contractOrContracts - Single contract or array of contracts
265
- */
266
- const configureAlgo = async (account, contractOrContracts) => {
267
- const contractList = Array.isArray(contractOrContracts) ? contractOrContracts : [contractOrContracts];
268
- const isMultiSymbol = contractList.length > 1;
269
-
270
- console.log();
271
- console.log(chalk.cyan(' CONFIGURE ALGO PARAMETERS'));
272
-
273
- // Show selected symbols with quantities (multi-symbol mode)
274
- if (isMultiSymbol) {
275
- console.log(chalk.white(` Trading ${contractList.length} symbols:`));
276
- contractList.forEach((c, i) => {
277
- const name = c.name || c.symbol;
278
- const qty = c.qty || 1;
279
- console.log(chalk.yellow(` ${i + 1}. ${name} x${qty}`));
280
- });
281
- }
282
- console.log();
283
-
284
- // Only ask for contracts in single-symbol mode
285
- // In multi-symbol mode, qty is already set per symbol
286
- let contracts = 1;
287
- if (!isMultiSymbol) {
288
- contracts = await prompts.numberInput('NUMBER OF CONTRACTS:', 1, 1, 10);
289
- if (contracts === null) return null;
290
- }
291
-
292
- const dailyTarget = await prompts.numberInput('DAILY TARGET ($):', 1000, 1, 10000);
293
- if (dailyTarget === null) return null;
294
-
295
- const maxRisk = await prompts.numberInput('MAX RISK ($):', 500, 1, 5000);
296
- if (maxRisk === null) return null;
297
-
298
- const showName = await prompts.confirmPrompt('SHOW ACCOUNT NAME?', false);
299
- if (showName === null) return null;
300
-
301
- // Check if AI agents are available
302
- const aiAgents = aiService.getAgents();
303
- let enableAI = false;
304
-
305
- if (aiAgents.length > 0) {
306
- // Show available agents
307
- console.log();
308
- console.log(chalk.magenta(` ${aiAgents.length} AI AGENT(S) AVAILABLE:`));
309
- aiAgents.forEach((agent, i) => {
310
- const modelInfo = agent.model ? chalk.gray(` (${agent.model})`) : '';
311
- console.log(chalk.white(` ${i + 1}. ${agent.name}${modelInfo}`));
312
- });
313
- console.log();
314
-
315
- enableAI = await prompts.confirmPrompt('ACTIVATE AI MODELS?', true);
316
- if (enableAI === null) return null;
317
-
318
- if (enableAI) {
319
- const mode = aiAgents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
320
- console.log(chalk.green(` AI MODE: ${mode} (${aiAgents.length} agent${aiAgents.length > 1 ? 's' : ''})`));
321
- } else {
322
- console.log(chalk.gray(' AI AGENTS DISABLED FOR THIS SESSION'));
323
- }
324
- }
325
-
326
- console.log();
327
- const confirm = await prompts.confirmPrompt('START ALGO TRADING?', true);
328
- if (!confirm) return null;
329
-
330
- // Show spinner while initializing
331
- const initSpinner = ora({ text: 'INITIALIZING ALGO TRADING...', color: 'yellow' }).start();
332
- await new Promise(r => setTimeout(r, 500));
333
- initSpinner.succeed('LAUNCHING ALGO...');
334
-
335
- return { contracts, dailyTarget, maxRisk, showName, enableAI };
336
- };
337
-
338
110
  /**
339
111
  * Launch algo trading - HQX Ultra Scalping Strategy
340
112
  * Real-time market data + Strategy signals + Auto order execution