hedgequantx 2.7.87 → 2.7.89
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/pages/algo/custom-strategy.js +379 -0
- package/src/pages/algo/index.js +42 -39
- package/src/ui/index.js +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Strategy - AI-powered strategy builder
|
|
3
|
+
* Same config flow as One Account, then AI chat to create strategy
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const ora = require('ora');
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
|
|
13
|
+
const { getLogoWidth, centerText, displayBanner } = require('../../ui');
|
|
14
|
+
const { prompts } = require('../../utils');
|
|
15
|
+
const { connections } = require('../../services');
|
|
16
|
+
const { getActiveProvider } = require('../ai-agents');
|
|
17
|
+
const cliproxy = require('../../services/cliproxy');
|
|
18
|
+
const { checkMarketHours } = require('../../services/rithmic/market');
|
|
19
|
+
|
|
20
|
+
// Strategies directory
|
|
21
|
+
const STRATEGIES_DIR = path.join(os.homedir(), '.hqx', 'strategies');
|
|
22
|
+
|
|
23
|
+
/** Ensure strategies directory exists */
|
|
24
|
+
const ensureStrategiesDir = () => {
|
|
25
|
+
if (!fs.existsSync(STRATEGIES_DIR)) fs.mkdirSync(STRATEGIES_DIR, { recursive: true });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Custom Strategy Menu */
|
|
29
|
+
const customStrategyMenu = async (service) => {
|
|
30
|
+
// Check AI provider first
|
|
31
|
+
const aiProvider = getActiveProvider();
|
|
32
|
+
if (!aiProvider) {
|
|
33
|
+
console.log(chalk.red('\n No AI Agent connected. Go to AI Agents menu first.'));
|
|
34
|
+
await prompts.waitForEnter();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check market hours
|
|
39
|
+
const market = checkMarketHours();
|
|
40
|
+
if (!market.isOpen && !market.message.includes('early')) {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(chalk.red(` ${market.message}`));
|
|
43
|
+
console.log(chalk.gray(' Custom strategy requires market to be open'));
|
|
44
|
+
console.log();
|
|
45
|
+
await prompts.waitForEnter();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Step 1: Fetch accounts
|
|
50
|
+
const spinner = ora({ text: 'Fetching active accounts...', color: 'yellow' }).start();
|
|
51
|
+
const allAccounts = await connections.getAllAccounts();
|
|
52
|
+
|
|
53
|
+
if (!allAccounts?.length) {
|
|
54
|
+
spinner.fail('No accounts found');
|
|
55
|
+
await prompts.waitForEnter();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const activeAccounts = allAccounts.filter(acc => acc.status === 0);
|
|
60
|
+
if (!activeAccounts.length) {
|
|
61
|
+
spinner.fail('No active accounts');
|
|
62
|
+
await prompts.waitForEnter();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
|
|
67
|
+
|
|
68
|
+
// Step 2: Select account
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.cyan.bold(' STEP 1: SELECT ACCOUNT'));
|
|
71
|
+
const accountOptions = activeAccounts.map(acc => {
|
|
72
|
+
const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
|
|
73
|
+
const balance = acc.balance !== null && acc.balance !== undefined
|
|
74
|
+
? ` - $${acc.balance.toLocaleString()}` : '';
|
|
75
|
+
return {
|
|
76
|
+
label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`,
|
|
77
|
+
value: acc
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
accountOptions.push({ label: '< Back', value: 'back' });
|
|
81
|
+
|
|
82
|
+
const selectedAccount = await prompts.selectOption('Select Account:', accountOptions);
|
|
83
|
+
if (!selectedAccount || selectedAccount === 'back') return;
|
|
84
|
+
|
|
85
|
+
const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
|
|
86
|
+
|
|
87
|
+
// Step 3: Select symbol
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.cyan.bold(' STEP 2: SELECT SYMBOL'));
|
|
90
|
+
const contract = await selectSymbol(accountService);
|
|
91
|
+
if (!contract) return;
|
|
92
|
+
|
|
93
|
+
// Step 4: Configure parameters
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(chalk.cyan.bold(' STEP 3: CONFIGURE PARAMETERS'));
|
|
96
|
+
console.log();
|
|
97
|
+
|
|
98
|
+
const contracts = await prompts.numberInput('Number of contracts:', 1, 1, 10);
|
|
99
|
+
if (contracts === null) return;
|
|
100
|
+
|
|
101
|
+
const dailyTarget = await prompts.numberInput('Daily target ($):', 200, 1, 10000);
|
|
102
|
+
if (dailyTarget === null) return;
|
|
103
|
+
|
|
104
|
+
const maxRisk = await prompts.numberInput('Max risk ($):', 100, 1, 5000);
|
|
105
|
+
if (maxRisk === null) return;
|
|
106
|
+
|
|
107
|
+
const showName = await prompts.confirmPrompt('Show account name?', false);
|
|
108
|
+
if (showName === null) return;
|
|
109
|
+
|
|
110
|
+
// Step 5: AI Supervision
|
|
111
|
+
console.log();
|
|
112
|
+
console.log(chalk.cyan.bold(' STEP 4: AI SUPERVISION'));
|
|
113
|
+
const aiSupervision = await prompts.confirmPrompt('Enable AI supervision during execution?', true);
|
|
114
|
+
if (aiSupervision === null) return;
|
|
115
|
+
|
|
116
|
+
const config = {
|
|
117
|
+
account: selectedAccount,
|
|
118
|
+
contract,
|
|
119
|
+
contracts,
|
|
120
|
+
dailyTarget,
|
|
121
|
+
maxRisk,
|
|
122
|
+
showName,
|
|
123
|
+
aiSupervision,
|
|
124
|
+
aiProvider
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Step 6: AI Chat to create/configure strategy
|
|
128
|
+
console.log();
|
|
129
|
+
console.log(chalk.cyan.bold(' STEP 5: CREATE YOUR STRATEGY WITH AI'));
|
|
130
|
+
console.log(chalk.gray(' Describe your trading strategy in plain English.'));
|
|
131
|
+
console.log(chalk.gray(' The AI will help you build and validate it.'));
|
|
132
|
+
console.log();
|
|
133
|
+
|
|
134
|
+
await strategyChat(config, accountService);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** Select symbol - same as one-account */
|
|
138
|
+
const selectSymbol = async (service) => {
|
|
139
|
+
const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
|
|
140
|
+
|
|
141
|
+
const contractsResult = await service.getContracts();
|
|
142
|
+
if (!contractsResult.success || !contractsResult.contracts?.length) {
|
|
143
|
+
spinner.fail('Failed to load contracts');
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let contracts = contractsResult.contracts;
|
|
148
|
+
|
|
149
|
+
// Sort popular indices first
|
|
150
|
+
const popular = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
|
|
151
|
+
contracts.sort((a, b) => {
|
|
152
|
+
const baseA = a.baseSymbol || a.symbol || '';
|
|
153
|
+
const baseB = b.baseSymbol || b.symbol || '';
|
|
154
|
+
const idxA = popular.findIndex(p => baseA === p || baseA.startsWith(p));
|
|
155
|
+
const idxB = popular.findIndex(p => baseB === p || baseB.startsWith(p));
|
|
156
|
+
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
|
|
157
|
+
if (idxA !== -1) return -1;
|
|
158
|
+
if (idxB !== -1) return 1;
|
|
159
|
+
return baseA.localeCompare(baseB);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
spinner.succeed(`Found ${contracts.length} contracts`);
|
|
163
|
+
|
|
164
|
+
const options = contracts.map(c => ({
|
|
165
|
+
label: `${c.symbol} - ${c.name} (${c.exchange})`,
|
|
166
|
+
value: c
|
|
167
|
+
}));
|
|
168
|
+
options.push({ label: chalk.gray('< Back'), value: 'back' });
|
|
169
|
+
|
|
170
|
+
const selected = await prompts.selectOption(chalk.yellow('Select Symbol:'), options);
|
|
171
|
+
return selected === 'back' || selected === null ? null : selected;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** AI Chat for strategy creation */
|
|
175
|
+
const strategyChat = async (config, service) => {
|
|
176
|
+
const { account, contract, contracts, dailyTarget, maxRisk, showName, aiSupervision, aiProvider } = config;
|
|
177
|
+
|
|
178
|
+
const accountName = showName
|
|
179
|
+
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
180
|
+
: 'HQX *****';
|
|
181
|
+
|
|
182
|
+
console.clear();
|
|
183
|
+
displayBanner();
|
|
184
|
+
|
|
185
|
+
const boxWidth = getLogoWidth();
|
|
186
|
+
const W = boxWidth - 2;
|
|
187
|
+
|
|
188
|
+
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
189
|
+
console.log(chalk.cyan('║') + chalk.green.bold(centerText('CUSTOM STRATEGY - AI CHAT', W)) + chalk.cyan('║'));
|
|
190
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
191
|
+
console.log(chalk.cyan('║') + centerText(`Account: ${accountName} | Symbol: ${contract.name} | Qty: ${contracts}`, W) + chalk.cyan('║'));
|
|
192
|
+
console.log(chalk.cyan('║') + centerText(`Target: $${dailyTarget} | Risk: $${maxRisk} | AI Supervision: ${aiSupervision ? 'ON' : 'OFF'}`, W) + chalk.cyan('║'));
|
|
193
|
+
console.log(chalk.cyan('║') + chalk.gray(centerText(`AI: ${aiProvider.name} (${aiProvider.modelName || 'default'})`, W)) + chalk.cyan('║'));
|
|
194
|
+
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
195
|
+
console.log(chalk.cyan('║') + chalk.gray(centerText('Type your strategy. "run" to execute, "save" to save, "cancel" to abort', W)) + chalk.cyan('║'));
|
|
196
|
+
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
197
|
+
console.log();
|
|
198
|
+
|
|
199
|
+
const systemPrompt = `You are an expert algorithmic trading assistant for futures trading.
|
|
200
|
+
|
|
201
|
+
Current setup:
|
|
202
|
+
- Account: ${accountName}
|
|
203
|
+
- Symbol: ${contract.name} (${contract.symbol})
|
|
204
|
+
- Contracts: ${contracts}
|
|
205
|
+
- Daily Target: $${dailyTarget}
|
|
206
|
+
- Max Risk: $${maxRisk}
|
|
207
|
+
- AI Supervision: ${aiSupervision ? 'Enabled' : 'Disabled'}
|
|
208
|
+
|
|
209
|
+
Help the user create a trading strategy. When they describe what they want:
|
|
210
|
+
1. Understand their entry/exit logic
|
|
211
|
+
2. Validate the strategy makes sense
|
|
212
|
+
3. Suggest improvements if needed
|
|
213
|
+
4. When ready, confirm the strategy is valid
|
|
214
|
+
|
|
215
|
+
Keep responses concise (2-3 sentences max unless explaining strategy details).
|
|
216
|
+
When the user says "run", output: [STRATEGY_READY]
|
|
217
|
+
Include the strategy parameters in JSON format when ready.`;
|
|
218
|
+
|
|
219
|
+
const messages = [{ role: 'system', content: systemPrompt }];
|
|
220
|
+
let strategyReady = false;
|
|
221
|
+
let strategyConfig = null;
|
|
222
|
+
|
|
223
|
+
console.log(chalk.green(' AI: ') + `Hello! I'll help you create a custom strategy for ${contract.name}.`);
|
|
224
|
+
console.log(chalk.green(' ') + 'What kind of trading strategy do you want to build?');
|
|
225
|
+
console.log();
|
|
226
|
+
|
|
227
|
+
while (true) {
|
|
228
|
+
const userInput = await prompts.textInput(chalk.yellow(' You: '));
|
|
229
|
+
|
|
230
|
+
if (!userInput) continue;
|
|
231
|
+
|
|
232
|
+
const cmd = userInput.toLowerCase().trim();
|
|
233
|
+
|
|
234
|
+
if (cmd === 'cancel' || cmd === 'exit' || cmd === 'quit') {
|
|
235
|
+
console.log(chalk.gray('\n Strategy creation cancelled.'));
|
|
236
|
+
await prompts.waitForEnter();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (cmd === 'save') {
|
|
241
|
+
if (strategyConfig) {
|
|
242
|
+
await saveStrategy(strategyConfig, config);
|
|
243
|
+
} else {
|
|
244
|
+
console.log(chalk.yellow('\n No strategy to save yet. Keep describing your strategy.'));
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (cmd === 'run') {
|
|
250
|
+
if (strategyReady && strategyConfig) {
|
|
251
|
+
console.log(chalk.green('\n Launching strategy...'));
|
|
252
|
+
await launchCustomStrategy(config, strategyConfig, service);
|
|
253
|
+
return;
|
|
254
|
+
} else {
|
|
255
|
+
console.log(chalk.yellow('\n Strategy not ready yet. Describe your entry/exit conditions first.'));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
messages.push({ role: 'user', content: userInput });
|
|
261
|
+
|
|
262
|
+
const spinner = ora({ text: 'AI thinking...', color: 'yellow' }).start();
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const modelId = aiProvider.modelId || getDefaultModel(aiProvider.id);
|
|
266
|
+
const result = await cliproxy.chatCompletion(modelId, messages);
|
|
267
|
+
|
|
268
|
+
if (!result.success) {
|
|
269
|
+
spinner.fail(`AI Error: ${result.error}`);
|
|
270
|
+
messages.pop();
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const response = result.response?.choices?.[0]?.message?.content || '';
|
|
275
|
+
messages.push({ role: 'assistant', content: response });
|
|
276
|
+
|
|
277
|
+
spinner.stop();
|
|
278
|
+
console.log();
|
|
279
|
+
console.log(chalk.green(' AI: ') + formatResponse(response));
|
|
280
|
+
console.log();
|
|
281
|
+
|
|
282
|
+
// Check if strategy is ready
|
|
283
|
+
if (response.includes('[STRATEGY_READY]') || response.toLowerCase().includes('strategy is ready')) {
|
|
284
|
+
strategyReady = true;
|
|
285
|
+
// Try to extract JSON config
|
|
286
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
287
|
+
if (jsonMatch) {
|
|
288
|
+
try { strategyConfig = JSON.parse(jsonMatch[0]); } catch (e) {}
|
|
289
|
+
}
|
|
290
|
+
if (!strategyConfig) {
|
|
291
|
+
strategyConfig = { description: userInput, messages: messages.slice(1) };
|
|
292
|
+
}
|
|
293
|
+
console.log(chalk.cyan(' [Strategy ready! Type "run" to execute or "save" to save for later]'));
|
|
294
|
+
console.log();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
} catch (e) {
|
|
298
|
+
spinner.fail(`Error: ${e.message}`);
|
|
299
|
+
messages.pop();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/** Get default model for provider */
|
|
305
|
+
const getDefaultModel = (providerId) => {
|
|
306
|
+
const defaults = { anthropic: 'claude-sonnet-4-20250514', google: 'gemini-2.5-pro', openai: 'gpt-4o' };
|
|
307
|
+
return defaults[providerId] || 'claude-sonnet-4-20250514';
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/** Format AI response */
|
|
311
|
+
const formatResponse = (text) => {
|
|
312
|
+
const clean = text.replace(/\[STRATEGY_READY\]/g, '').trim();
|
|
313
|
+
const lines = clean.split('\n');
|
|
314
|
+
return lines.map((l, i) => i === 0 ? l : ' ' + l).join('\n');
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/** Save strategy to disk */
|
|
318
|
+
const saveStrategy = async (strategyConfig, config) => {
|
|
319
|
+
ensureStrategiesDir();
|
|
320
|
+
|
|
321
|
+
const name = await prompts.textInput(chalk.cyan(' Strategy name: '));
|
|
322
|
+
if (!name || !name.trim()) {
|
|
323
|
+
console.log(chalk.gray(' Save cancelled.'));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const folderName = name.trim().toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
328
|
+
const strategyPath = path.join(STRATEGIES_DIR, folderName);
|
|
329
|
+
|
|
330
|
+
if (!fs.existsSync(strategyPath)) fs.mkdirSync(strategyPath, { recursive: true });
|
|
331
|
+
|
|
332
|
+
const configFile = {
|
|
333
|
+
name: name.trim(),
|
|
334
|
+
symbol: config.contract.name,
|
|
335
|
+
contracts: config.contracts,
|
|
336
|
+
dailyTarget: config.dailyTarget,
|
|
337
|
+
maxRisk: config.maxRisk,
|
|
338
|
+
aiSupervision: config.aiSupervision,
|
|
339
|
+
strategy: strategyConfig,
|
|
340
|
+
createdAt: new Date().toISOString()
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
fs.writeFileSync(path.join(strategyPath, 'config.json'), JSON.stringify(configFile, null, 2));
|
|
344
|
+
console.log(chalk.green(`\n ✓ Strategy saved: ${strategyPath}`));
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/** Launch custom strategy execution */
|
|
348
|
+
const launchCustomStrategy = async (config, strategyConfig, service) => {
|
|
349
|
+
const { account, contract, contracts, dailyTarget, maxRisk, showName, aiSupervision, aiProvider } = config;
|
|
350
|
+
|
|
351
|
+
console.log(chalk.yellow('\n Custom strategy execution coming soon...'));
|
|
352
|
+
console.log(chalk.gray(' Your strategy will use the HQX engine with AI supervision.'));
|
|
353
|
+
console.log();
|
|
354
|
+
console.log(chalk.white(' Strategy Summary:'));
|
|
355
|
+
console.log(chalk.gray(` - Symbol: ${contract.name}`));
|
|
356
|
+
console.log(chalk.gray(` - Contracts: ${contracts}`));
|
|
357
|
+
console.log(chalk.gray(` - Target: $${dailyTarget}`));
|
|
358
|
+
console.log(chalk.gray(` - Risk: $${maxRisk}`));
|
|
359
|
+
console.log(chalk.gray(` - AI Supervision: ${aiSupervision ? 'Enabled' : 'Disabled'}`));
|
|
360
|
+
|
|
361
|
+
await prompts.waitForEnter();
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/** Load saved strategies */
|
|
365
|
+
const loadStrategies = () => {
|
|
366
|
+
ensureStrategiesDir();
|
|
367
|
+
try {
|
|
368
|
+
const items = fs.readdirSync(STRATEGIES_DIR, { withFileTypes: true });
|
|
369
|
+
return items.filter(i => i.isDirectory()).map(dir => {
|
|
370
|
+
const configPath = path.join(STRATEGIES_DIR, dir.name, 'config.json');
|
|
371
|
+
if (fs.existsSync(configPath)) {
|
|
372
|
+
return { folder: dir.name, ...JSON.parse(fs.readFileSync(configPath, 'utf8')) };
|
|
373
|
+
}
|
|
374
|
+
return { folder: dir.name, name: dir.name };
|
|
375
|
+
});
|
|
376
|
+
} catch (e) { return []; }
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
module.exports = { customStrategyMenu, loadStrategies };
|
package/src/pages/algo/index.js
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const { getLogoWidth, centerText, displayBanner } = require('../../ui');
|
|
7
7
|
const { logger, prompts } = require('../../utils');
|
|
8
|
+
const { getActiveProvider } = require('../ai-agents');
|
|
8
9
|
|
|
9
10
|
const log = logger.scope('AlgoMenu');
|
|
10
11
|
|
|
11
12
|
const { oneAccountMenu } = require('./one-account');
|
|
12
13
|
const { copyTradingMenu } = require('./copy-trading');
|
|
14
|
+
const { customStrategyMenu } = require('./custom-strategy');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Algo Trading Menu
|
|
@@ -25,33 +27,52 @@ const algoTradingMenu = async (service) => {
|
|
|
25
27
|
const boxWidth = getLogoWidth();
|
|
26
28
|
const W = boxWidth - 2;
|
|
27
29
|
|
|
30
|
+
// Check if AI agent is connected
|
|
31
|
+
const aiProvider = getActiveProvider();
|
|
32
|
+
const hasAI = !!aiProvider;
|
|
33
|
+
|
|
28
34
|
// Draw menu rectangle
|
|
29
35
|
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
30
36
|
console.log(chalk.cyan('║') + chalk.magenta.bold(centerText('ALGO-TRADING', W)) + chalk.cyan('║'));
|
|
31
37
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
32
38
|
|
|
33
|
-
// 3 columns layout
|
|
39
|
+
// 2 or 3 columns layout based on AI availability
|
|
34
40
|
const col1 = '[1] ONE ACCOUNT';
|
|
35
41
|
const col2 = '[2] COPY TRADING';
|
|
36
|
-
const col3 = '[3] CUSTOM STRATEGY';
|
|
37
|
-
const colWidth = Math.floor(W / 3);
|
|
38
|
-
const lastColWidth = W - 2 * colWidth;
|
|
39
|
-
|
|
40
|
-
const pad1 = Math.floor((colWidth - col1.length) / 2);
|
|
41
|
-
const pad2 = Math.floor((colWidth - col2.length) / 2);
|
|
42
|
-
const pad3 = Math.floor((lastColWidth - col3.length) / 2);
|
|
43
|
-
|
|
44
|
-
const col1Str = ' '.repeat(pad1) + chalk.cyan(col1) + ' '.repeat(colWidth - col1.length - pad1);
|
|
45
|
-
const col2Str = ' '.repeat(pad2) + chalk.yellow(col2) + ' '.repeat(colWidth - col2.length - pad2);
|
|
46
|
-
const col3Str = ' '.repeat(pad3) + chalk.green(col3) + ' '.repeat(lastColWidth - col3.length - pad3);
|
|
42
|
+
const col3 = hasAI ? '[3] CUSTOM STRATEGY' : '';
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
if (hasAI) {
|
|
45
|
+
// 3 columns
|
|
46
|
+
const colWidth = Math.floor(W / 3);
|
|
47
|
+
const lastColWidth = W - 2 * colWidth;
|
|
48
|
+
|
|
49
|
+
const pad1 = Math.floor((colWidth - col1.length) / 2);
|
|
50
|
+
const pad2 = Math.floor((colWidth - col2.length) / 2);
|
|
51
|
+
const pad3 = Math.floor((lastColWidth - col3.length) / 2);
|
|
52
|
+
|
|
53
|
+
const col1Str = ' '.repeat(pad1) + chalk.cyan(col1) + ' '.repeat(colWidth - col1.length - pad1);
|
|
54
|
+
const col2Str = ' '.repeat(pad2) + chalk.yellow(col2) + ' '.repeat(colWidth - col2.length - pad2);
|
|
55
|
+
const col3Str = ' '.repeat(pad3) + chalk.green(col3) + ' '.repeat(lastColWidth - col3.length - pad3);
|
|
56
|
+
|
|
57
|
+
console.log(chalk.cyan('║') + col1Str + col2Str + col3Str + chalk.cyan('║'));
|
|
58
|
+
} else {
|
|
59
|
+
// 2 columns only (no AI connected)
|
|
60
|
+
const colWidth = Math.floor(W / 2);
|
|
61
|
+
const pad1 = Math.floor((colWidth - col1.length) / 2);
|
|
62
|
+
const pad2 = Math.floor((W - colWidth - col2.length) / 2);
|
|
63
|
+
|
|
64
|
+
const col1Str = ' '.repeat(pad1) + chalk.cyan(col1) + ' '.repeat(colWidth - col1.length - pad1);
|
|
65
|
+
const col2Str = ' '.repeat(pad2) + chalk.yellow(col2) + ' '.repeat(W - colWidth - col2.length - pad2);
|
|
66
|
+
|
|
67
|
+
console.log(chalk.cyan('║') + col1Str + col2Str + chalk.cyan('║'));
|
|
68
|
+
}
|
|
49
69
|
|
|
50
70
|
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
51
71
|
console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
|
|
52
72
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
53
73
|
|
|
54
|
-
const
|
|
74
|
+
const promptText = hasAI ? 'SELECT (1/2/3/B): ' : 'SELECT (1/2/B): ';
|
|
75
|
+
const input = await prompts.textInput(chalk.cyan(promptText));
|
|
55
76
|
const choice = (input || '').toLowerCase().trim();
|
|
56
77
|
|
|
57
78
|
log.debug('Algo mode selected', { choice });
|
|
@@ -70,8 +91,13 @@ const algoTradingMenu = async (service) => {
|
|
|
70
91
|
await copyTradingMenu();
|
|
71
92
|
break;
|
|
72
93
|
case '3':
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
if (hasAI) {
|
|
95
|
+
log.info('Starting Custom Strategy mode');
|
|
96
|
+
await customStrategyMenu(service);
|
|
97
|
+
} else {
|
|
98
|
+
console.log(chalk.red(' INVALID OPTION'));
|
|
99
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
100
|
+
}
|
|
75
101
|
break;
|
|
76
102
|
default:
|
|
77
103
|
console.log(chalk.red(' INVALID OPTION'));
|
|
@@ -87,27 +113,4 @@ const algoTradingMenu = async (service) => {
|
|
|
87
113
|
}
|
|
88
114
|
};
|
|
89
115
|
|
|
90
|
-
/**
|
|
91
|
-
* Custom Strategy Menu - AI-powered strategy creation
|
|
92
|
-
*/
|
|
93
|
-
const customStrategyMenu = async (service) => {
|
|
94
|
-
console.clear();
|
|
95
|
-
displayBanner();
|
|
96
|
-
|
|
97
|
-
const boxWidth = getLogoWidth();
|
|
98
|
-
const W = boxWidth - 2;
|
|
99
|
-
|
|
100
|
-
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
101
|
-
console.log(chalk.cyan('║') + chalk.green.bold(centerText('CUSTOM STRATEGY', W)) + chalk.cyan('║'));
|
|
102
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
103
|
-
console.log(chalk.cyan('║') + centerText('Create your own trading strategy with AI assistance', W) + chalk.cyan('║'));
|
|
104
|
-
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
105
|
-
console.log(chalk.cyan('║') + chalk.gray(centerText('Coming soon...', W)) + chalk.cyan('║'));
|
|
106
|
-
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
107
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
|
|
108
|
-
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
109
|
-
|
|
110
|
-
await prompts.waitForEnter();
|
|
111
|
-
};
|
|
112
|
-
|
|
113
116
|
module.exports = { algoTradingMenu };
|
package/src/ui/index.js
CHANGED
|
@@ -70,7 +70,7 @@ const displayBanner = () => {
|
|
|
70
70
|
|
|
71
71
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
72
72
|
const tagline = isMobile ? `HQX V${version}` : `PROP FUTURES ALGO TRADING V${version}`;
|
|
73
|
-
console.log(chalk.cyan('║') + chalk.
|
|
73
|
+
console.log(chalk.cyan('║') + chalk.yellow(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
74
74
|
|
|
75
75
|
// ALWAYS close the banner
|
|
76
76
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|