hedgequantx 2.6.157 → 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
|
@@ -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
|
+
};
|
|
@@ -28,7 +28,7 @@ const { RithmicMarketDataFeed } = require('../../services/rithmic/market-data');
|
|
|
28
28
|
const { algoLogger } = require('./logger');
|
|
29
29
|
const { recoveryMath } = require('../../services/strategy/recovery-math');
|
|
30
30
|
const aiService = require('../../services/ai');
|
|
31
|
-
const { launchMultiSymbolRithmic } = require('./one-account');
|
|
31
|
+
const { launchMultiSymbolRithmic, selectSymbol, configureAlgo } = require('./one-account');
|
|
32
32
|
const aiClient = require('../../services/ai/client');
|
|
33
33
|
|
|
34
34
|
// Strategy template that the AI will fill
|
|
@@ -391,111 +391,69 @@ const customStrategyMenu = async (service) => {
|
|
|
391
391
|
|
|
392
392
|
/**
|
|
393
393
|
* Execute trading with custom strategy
|
|
394
|
-
*
|
|
394
|
+
* Uses same flow as one-account.js
|
|
395
395
|
*/
|
|
396
396
|
async function executeWithCustomStrategy(service, StrategyClass, strategyName) {
|
|
397
|
-
const
|
|
398
|
-
const innerWidth = boxWidth - 2;
|
|
397
|
+
const { connections } = require('../../services');
|
|
399
398
|
|
|
400
|
-
// Get accounts
|
|
401
|
-
const
|
|
402
|
-
if (!
|
|
399
|
+
// Get all accounts (same as one-account.js)
|
|
400
|
+
const allAccounts = await connections.getAllAccounts();
|
|
401
|
+
if (!allAccounts || allAccounts.length === 0) {
|
|
403
402
|
console.log(chalk.red('\n No trading accounts available.\n'));
|
|
404
403
|
await prompts.waitForEnter();
|
|
405
404
|
return;
|
|
406
405
|
}
|
|
407
|
-
|
|
408
|
-
// Account selection
|
|
409
|
-
console.log(chalk.cyan('\n SELECT ACCOUNT:\n'));
|
|
410
|
-
accountsResult.accounts.forEach((acc, i) => {
|
|
411
|
-
const balance = acc.balance !== null ? `$${acc.balance.toLocaleString()}` : 'N/A';
|
|
412
|
-
const propfirm = acc.propfirm || 'Unknown';
|
|
413
|
-
const accId = acc.rithmicAccountId || acc.accountId;
|
|
414
|
-
console.log(chalk.cyan(` [${i + 1}]`) + chalk.yellow(` ${propfirm}`) + chalk.gray(` (${accId})`) + chalk.green(` ${balance}`));
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
const accChoice = await prompts.numberInput('\n ACCOUNT #:', 1, 1, accountsResult.accounts.length);
|
|
418
|
-
if (accChoice === null) return;
|
|
419
|
-
const account = accountsResult.accounts[accChoice - 1];
|
|
420
|
-
|
|
421
|
-
// Symbol selection
|
|
422
|
-
const symbolInput = await prompts.textInput(chalk.cyan(' SYMBOL (e.g., MES, MNQ): '));
|
|
423
|
-
if (!symbolInput) return;
|
|
424
|
-
const symbols = symbolInput.toUpperCase().split(',').map(s => s.trim()).filter(Boolean);
|
|
425
|
-
|
|
426
|
-
// Target and Risk
|
|
427
|
-
const dailyTarget = await prompts.numberInput(' TARGET ($):', 1000, 100, 50000);
|
|
428
|
-
if (dailyTarget === null) return;
|
|
429
|
-
|
|
430
|
-
const maxRisk = await prompts.numberInput(' MAX RISK ($):', 500, 50, 10000);
|
|
431
|
-
if (maxRisk === null) return;
|
|
432
|
-
|
|
433
|
-
// Confirm
|
|
434
|
-
console.log(chalk.cyan('\n ╔════════════════════════════════════════════════════════════╗'));
|
|
435
|
-
console.log(chalk.cyan(' ║') + chalk.yellow.bold(' CONFIRM SETTINGS ') + chalk.cyan('║'));
|
|
436
|
-
console.log(chalk.cyan(' ╠════════════════════════════════════════════════════════════╣'));
|
|
437
|
-
console.log(chalk.cyan(' ║') + chalk.white(` Strategy: ${strategyName.substring(0, 47).padEnd(47)}`) + chalk.cyan('║'));
|
|
438
|
-
console.log(chalk.cyan(' ║') + chalk.white(` Account: ${(account.name || account.accountId).substring(0, 47).padEnd(47)}`) + chalk.cyan('║'));
|
|
439
|
-
console.log(chalk.cyan(' ║') + chalk.white(` Symbols: ${symbols.join(', ').substring(0, 47).padEnd(47)}`) + chalk.cyan('║'));
|
|
440
|
-
console.log(chalk.cyan(' ║') + chalk.green(` Target: $${dailyTarget.toLocaleString().padEnd(46)}`) + chalk.cyan('║'));
|
|
441
|
-
console.log(chalk.cyan(' ║') + chalk.red(` Risk: $${maxRisk.toLocaleString().padEnd(46)}`) + chalk.cyan('║'));
|
|
442
|
-
console.log(chalk.cyan(' ╚════════════════════════════════════════════════════════════╝\n'));
|
|
443
|
-
|
|
444
|
-
const startConfirm = await prompts.textInput(chalk.cyan(' START TRADING? (Y/n): '));
|
|
445
|
-
if (startConfirm.toLowerCase() === 'n') {
|
|
446
|
-
console.log(chalk.yellow('\n Cancelled.\n'));
|
|
447
|
-
await prompts.waitForEnter();
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Launch with custom strategy
|
|
452
|
-
console.log(chalk.green('\n Starting custom strategy trading...\n'));
|
|
453
406
|
|
|
454
|
-
//
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const frontMonth = await service.getFrontMonthContract(symbol);
|
|
459
|
-
if (frontMonth && frontMonth.success) {
|
|
460
|
-
contracts.push({
|
|
461
|
-
name: frontMonth.symbol || symbol,
|
|
462
|
-
symbol: frontMonth.symbol || symbol,
|
|
463
|
-
exchange: frontMonth.exchange || 'CME',
|
|
464
|
-
id: frontMonth.contractId || symbol,
|
|
465
|
-
tickSize: frontMonth.tickSize,
|
|
466
|
-
tickValue: frontMonth.tickValue,
|
|
467
|
-
qty: 1, // Default 1 contract per symbol
|
|
468
|
-
});
|
|
469
|
-
} else {
|
|
470
|
-
// Fallback - use symbol directly
|
|
471
|
-
contracts.push({
|
|
472
|
-
name: symbol,
|
|
473
|
-
symbol: symbol,
|
|
474
|
-
exchange: 'CME',
|
|
475
|
-
id: symbol,
|
|
476
|
-
tickSize: null,
|
|
477
|
-
tickValue: null,
|
|
478
|
-
qty: 1,
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (contracts.length === 0) {
|
|
484
|
-
console.log(chalk.red('\n No valid contracts found.\n'));
|
|
407
|
+
// Filter active accounts
|
|
408
|
+
const activeAccounts = allAccounts.filter(acc => acc.status === 'active' || !acc.status);
|
|
409
|
+
if (activeAccounts.length === 0) {
|
|
410
|
+
console.log(chalk.red('\n No active trading accounts.\n'));
|
|
485
411
|
await prompts.waitForEnter();
|
|
486
412
|
return;
|
|
487
413
|
}
|
|
488
414
|
|
|
489
|
-
//
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
415
|
+
// Account selection - same format as one-account.js
|
|
416
|
+
const options = activeAccounts.map(acc => {
|
|
417
|
+
const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
|
|
418
|
+
const balance = acc.balance !== null && acc.balance !== undefined
|
|
419
|
+
? ` - $${acc.balance.toLocaleString()}`
|
|
420
|
+
: '';
|
|
421
|
+
return {
|
|
422
|
+
label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`,
|
|
423
|
+
value: acc
|
|
424
|
+
};
|
|
425
|
+
});
|
|
426
|
+
options.push({ label: '< BACK', value: 'back' });
|
|
427
|
+
|
|
428
|
+
const selectedAccount = await prompts.selectOption('SELECT ACCOUNT:', options);
|
|
429
|
+
if (!selectedAccount || selectedAccount === 'back') return;
|
|
430
|
+
|
|
431
|
+
// Get the service for this account
|
|
432
|
+
const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
|
|
433
|
+
|
|
434
|
+
// Select symbol(s) - same as one-account.js
|
|
435
|
+
console.log();
|
|
436
|
+
const useMultiSymbol = await prompts.confirmPrompt(chalk.cyan('MULTI-SYMBOL MODE? (up to 5 symbols)'), false);
|
|
437
|
+
if (useMultiSymbol === null) return;
|
|
438
|
+
|
|
439
|
+
const contracts = await selectSymbol(accountService, selectedAccount, useMultiSymbol);
|
|
440
|
+
if (!contracts) return;
|
|
441
|
+
|
|
442
|
+
// Normalize to array
|
|
443
|
+
const contractList = Array.isArray(contracts) ? contracts : [contracts];
|
|
444
|
+
|
|
445
|
+
// Configure algo - same as one-account.js
|
|
446
|
+
const config = await configureAlgo(selectedAccount, contractList);
|
|
447
|
+
if (!config) return;
|
|
448
|
+
|
|
449
|
+
// Override: Custom strategy doesn't need AI supervisor (it IS the AI strategy)
|
|
450
|
+
config.enableAI = false;
|
|
451
|
+
|
|
452
|
+
// Show custom strategy info
|
|
453
|
+
console.log(chalk.magenta(`\n CUSTOM STRATEGY: ${strategyName}\n`));
|
|
454
|
+
|
|
497
455
|
// Launch with custom strategy class
|
|
498
|
-
await launchMultiSymbolRithmic(
|
|
456
|
+
await launchMultiSymbolRithmic(accountService, selectedAccount, contractList, config, StrategyClass);
|
|
499
457
|
}
|
|
500
458
|
|
|
501
459
|
module.exports = { customStrategyMenu, generateStrategyCode, validateStrategyCode };
|
|
@@ -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
|
-
|
|
37
|
-
|
|
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
|
|
@@ -2194,4 +1966,4 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config, Cus
|
|
|
2194
1966
|
await prompts.waitForEnter();
|
|
2195
1967
|
};
|
|
2196
1968
|
|
|
2197
|
-
module.exports = { oneAccountMenu, launchMultiSymbolRithmic };
|
|
1969
|
+
module.exports = { oneAccountMenu, launchMultiSymbolRithmic, selectSymbol, configureAlgo };
|