hedgequantx 2.7.88 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.7.88",
3
+ "version": "2.7.89",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Custom Strategy - AI-powered modular strategy builder
3
- * Each strategy is a folder with modular components
2
+ * Custom Strategy - AI-powered strategy builder
3
+ * Same config flow as One Account, then AI chat to create strategy
4
4
  */
5
5
 
6
6
  const chalk = require('chalk');
@@ -8,13 +8,16 @@ const fs = require('fs');
8
8
  const path = require('path');
9
9
  const os = require('os');
10
10
  const ora = require('ora');
11
+ const readline = require('readline');
11
12
 
12
13
  const { getLogoWidth, centerText, displayBanner } = require('../../ui');
13
14
  const { prompts } = require('../../utils');
15
+ const { connections } = require('../../services');
14
16
  const { getActiveProvider } = require('../ai-agents');
15
17
  const cliproxy = require('../../services/cliproxy');
18
+ const { checkMarketHours } = require('../../services/rithmic/market');
16
19
 
17
- // Base strategies directory
20
+ // Strategies directory
18
21
  const STRATEGIES_DIR = path.join(os.homedir(), '.hqx', 'strategies');
19
22
 
20
23
  /** Ensure strategies directory exists */
@@ -22,175 +25,203 @@ const ensureStrategiesDir = () => {
22
25
  if (!fs.existsSync(STRATEGIES_DIR)) fs.mkdirSync(STRATEGIES_DIR, { recursive: true });
23
26
  };
24
27
 
25
- /** Load all saved strategies (folders) */
26
- const loadStrategies = () => {
27
- ensureStrategiesDir();
28
- try {
29
- const items = fs.readdirSync(STRATEGIES_DIR, { withFileTypes: true });
30
- return items.filter(i => i.isDirectory()).map(dir => {
31
- const configPath = path.join(STRATEGIES_DIR, dir.name, 'config.json');
32
- if (fs.existsSync(configPath)) {
33
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
34
- return { folder: dir.name, path: path.join(STRATEGIES_DIR, dir.name), ...config };
35
- }
36
- return { folder: dir.name, path: path.join(STRATEGIES_DIR, dir.name), name: dir.name };
37
- });
38
- } catch (e) { return []; }
39
- };
40
-
41
- /** Create strategy folder structure */
42
- const createStrategyFolder = (name) => {
43
- ensureStrategiesDir();
44
- const folderName = name.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-');
45
- const strategyPath = path.join(STRATEGIES_DIR, folderName);
46
-
47
- if (fs.existsSync(strategyPath)) {
48
- return { success: false, error: 'Strategy folder already exists', path: null };
49
- }
50
-
51
- fs.mkdirSync(strategyPath, { recursive: true });
52
- return { success: true, path: strategyPath, folder: folderName };
53
- };
54
-
55
- /** Save strategy module */
56
- const saveModule = (strategyPath, moduleName, content) => {
57
- const filePath = path.join(strategyPath, moduleName);
58
- fs.writeFileSync(filePath, typeof content === 'string' ? content : JSON.stringify(content, null, 2));
59
- return filePath;
60
- };
61
-
62
- /** Delete strategy folder */
63
- const deleteStrategy = (strategyPath) => {
64
- if (fs.existsSync(strategyPath)) {
65
- fs.rmSync(strategyPath, { recursive: true, force: true });
66
- return true;
67
- }
68
- return false;
69
- };
70
-
71
28
  /** Custom Strategy Menu */
72
29
  const customStrategyMenu = async (service) => {
73
- while (true) {
74
- console.clear();
75
- displayBanner();
76
-
77
- const boxWidth = getLogoWidth();
78
- const W = boxWidth - 2;
79
- const aiProvider = getActiveProvider();
80
- const strategies = loadStrategies();
81
-
82
- console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
83
- console.log(chalk.cyan('║') + chalk.green.bold(centerText('CUSTOM STRATEGY BUILDER', W)) + chalk.cyan('║'));
84
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
85
-
86
- // AI Status
87
- if (aiProvider) {
88
- const status = `AI: ${aiProvider.name} (${aiProvider.modelName || 'default'})`;
89
- console.log(chalk.cyan('║') + chalk.green(centerText('● ' + status, W)) + chalk.cyan('║'));
90
- } else {
91
- console.log(chalk.cyan('║') + chalk.red(centerText('○ NO AI AGENT CONNECTED', W)) + chalk.cyan('║'));
92
- }
93
-
94
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
95
-
96
- // Options
97
- const col1 = '[1] CREATE NEW';
98
- const col2 = `[2] MY STRATEGIES (${strategies.length})`;
99
- const colWidth = Math.floor(W / 2);
100
- const pad1 = Math.floor((colWidth - col1.length) / 2);
101
- const pad2 = Math.floor((W - colWidth - col2.length) / 2);
102
- console.log(chalk.cyan('║') +
103
- ' '.repeat(pad1) + chalk.yellow(col1) + ' '.repeat(colWidth - col1.length - pad1) +
104
- ' '.repeat(pad2) + chalk.cyan(col2) + ' '.repeat(W - colWidth - col2.length - pad2) +
105
- chalk.cyan('║'));
106
-
107
- console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
108
- console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
109
- console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
110
-
111
- const input = await prompts.textInput(chalk.cyan('SELECT (1/2/B): '));
112
- const choice = (input || '').toLowerCase().trim();
113
-
114
- if (choice === 'b' || choice === '') return;
115
-
116
- if (choice === '1') {
117
- if (!aiProvider) {
118
- console.log(chalk.red('\n Connect an AI Agent first (AI Agents menu)'));
119
- await prompts.waitForEnter();
120
- continue;
121
- }
122
- await createStrategyWizard(aiProvider);
123
- } else if (choice === '2') {
124
- await myStrategiesMenu(strategies, service);
125
- }
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;
126
36
  }
127
- };
128
-
129
- /** AI Wizard to create modular strategy */
130
- const createStrategyWizard = async (aiProvider) => {
131
- console.clear();
132
- displayBanner();
133
37
 
134
- const boxWidth = getLogoWidth();
135
- const W = boxWidth - 2;
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
+ }
136
48
 
137
- console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
138
- console.log(chalk.cyan('║') + chalk.green.bold(centerText('CREATE STRATEGY WITH AI', W)) + chalk.cyan('║'));
139
- console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
140
- console.log();
49
+ // Step 1: Fetch accounts
50
+ const spinner = ora({ text: 'Fetching active accounts...', color: 'yellow' }).start();
51
+ const allAccounts = await connections.getAllAccounts();
141
52
 
142
- // Step 1: Strategy name
143
- console.log(chalk.yellow(' STEP 1: Name your strategy'));
144
- const name = await prompts.textInput(chalk.cyan(' Strategy name: '));
145
- if (!name || !name.trim()) {
146
- console.log(chalk.red(' Cancelled'));
53
+ if (!allAccounts?.length) {
54
+ spinner.fail('No accounts found');
147
55
  await prompts.waitForEnter();
148
56
  return;
149
57
  }
150
58
 
151
- // Create folder
152
- const folder = createStrategyFolder(name.trim());
153
- if (!folder.success) {
154
- console.log(chalk.red(` Error: ${folder.error}`));
59
+ const activeAccounts = allAccounts.filter(acc => acc.status === 0);
60
+ if (!activeAccounts.length) {
61
+ spinner.fail('No active accounts');
155
62
  await prompts.waitForEnter();
156
63
  return;
157
64
  }
158
65
 
159
- console.log(chalk.green(` Created: ${folder.path}`));
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
160
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;
161
115
 
162
- // Step 2: Chat with AI to build strategy
163
- console.log(chalk.yellow(' STEP 2: Describe your strategy to the AI'));
164
- console.log(chalk.gray(' Type your strategy idea in plain English.'));
165
- console.log(chalk.gray(' The AI will help you build each module.'));
166
- console.log(chalk.gray(' Type "done" when finished, "cancel" to abort.'));
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.'));
167
132
  console.log();
168
133
 
169
- const systemPrompt = `You are an expert algo trading strategy builder for futures (ES, NQ, MES, MNQ, etc).
170
- Help the user create a modular trading strategy. Build these components:
134
+ await strategyChat(config, accountService);
135
+ };
171
136
 
172
- 1. ENTRY CONDITIONS - When to open a position (long/short signals)
173
- 2. EXIT CONDITIONS - Take profit, stop loss, trailing stops
174
- 3. RISK MANAGEMENT - Position sizing, max loss, max positions
175
- 4. FILTERS - Market conditions when NOT to trade
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
+ };
176
173
 
177
- Ask clarifying questions. Be concise. When ready, output each module.
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.
178
200
 
179
- For each module, output JavaScript code in this format:
180
- \`\`\`javascript:entry.js
181
- module.exports = {
182
- checkLongEntry: (data) => { /* return true/false */ },
183
- checkShortEntry: (data) => { /* return true/false */ }
184
- };
185
- \`\`\`
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'}
186
208
 
187
- The 'data' object contains: { price, bid, ask, volume, atr, ema20, ema50, rsi, macd, vwap, high, low, open, close }`;
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.`;
188
218
 
189
219
  const messages = [{ role: 'system', content: systemPrompt }];
190
- const modules = {};
220
+ let strategyReady = false;
221
+ let strategyConfig = null;
191
222
 
192
- console.log(chalk.green(' AI: ') + 'What kind of trading strategy do you want to create?');
193
- console.log(chalk.gray(' Example: "A mean reversion strategy that buys when RSI < 30"'));
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?');
194
225
  console.log();
195
226
 
196
227
  while (true) {
@@ -198,30 +229,32 @@ The 'data' object contains: { price, bid, ask, volume, atr, ema20, ema50, rsi, m
198
229
 
199
230
  if (!userInput) continue;
200
231
 
201
- if (userInput.toLowerCase() === 'cancel') {
202
- deleteStrategy(folder.path);
203
- console.log(chalk.gray('\n Strategy cancelled and folder deleted.'));
232
+ const cmd = userInput.toLowerCase().trim();
233
+
234
+ if (cmd === 'cancel' || cmd === 'exit' || cmd === 'quit') {
235
+ console.log(chalk.gray('\n Strategy creation cancelled.'));
204
236
  await prompts.waitForEnter();
205
237
  return;
206
238
  }
207
239
 
208
- if (userInput.toLowerCase() === 'done') {
209
- // Save config
210
- saveModule(folder.path, 'config.json', {
211
- name: name.trim(),
212
- description: modules.description || '',
213
- createdAt: new Date().toISOString(),
214
- modules: Object.keys(modules).filter(k => k !== 'description')
215
- });
216
-
217
- console.log(chalk.green('\n ✓ Strategy saved!'));
218
- console.log(chalk.cyan(` Location: ${folder.path}`));
219
- console.log(chalk.gray(' Modules created:'));
220
- for (const m of Object.keys(modules)) {
221
- if (m !== 'description') console.log(chalk.gray(` - ${m}`));
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;
222
257
  }
223
- await prompts.waitForEnter();
224
- return;
225
258
  }
226
259
 
227
260
  messages.push({ role: 'user', content: userInput });
@@ -243,25 +276,24 @@ The 'data' object contains: { price, bid, ask, volume, atr, ema20, ema50, rsi, m
243
276
 
244
277
  spinner.stop();
245
278
  console.log();
279
+ console.log(chalk.green(' AI: ') + formatResponse(response));
280
+ console.log();
246
281
 
247
- // Extract and save code modules
248
- const codeBlocks = response.matchAll(/```javascript:(\w+\.js)\n([\s\S]*?)```/g);
249
- for (const match of codeBlocks) {
250
- const [, filename, code] = match;
251
- saveModule(folder.path, filename, code.trim());
252
- modules[filename] = true;
253
- console.log(chalk.green(` ✓ Saved module: ${filename}`));
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();
254
295
  }
255
296
 
256
- // Extract description if present
257
- const descMatch = response.match(/description[:\s]*["']?([^"'\n]+)/i);
258
- if (descMatch) modules.description = descMatch[1];
259
-
260
- // Print AI response (without code blocks for cleaner output)
261
- const cleanResponse = response.replace(/```[\s\S]*?```/g, '[code saved]');
262
- console.log(chalk.green(' AI: ') + formatResponse(cleanResponse));
263
- console.log();
264
-
265
297
  } catch (e) {
266
298
  spinner.fail(`Error: ${e.message}`);
267
299
  messages.pop();
@@ -269,126 +301,79 @@ The 'data' object contains: { price, bid, ask, volume, atr, ema20, ema50, rsi, m
269
301
  }
270
302
  };
271
303
 
272
- /** Get default model */
304
+ /** Get default model for provider */
273
305
  const getDefaultModel = (providerId) => {
274
- const defaults = {
275
- anthropic: 'claude-sonnet-4-20250514',
276
- google: 'gemini-2.5-pro',
277
- openai: 'gpt-4o'
278
- };
306
+ const defaults = { anthropic: 'claude-sonnet-4-20250514', google: 'gemini-2.5-pro', openai: 'gpt-4o' };
279
307
  return defaults[providerId] || 'claude-sonnet-4-20250514';
280
308
  };
281
309
 
282
- /** Format response for terminal */
310
+ /** Format AI response */
283
311
  const formatResponse = (text) => {
284
- const lines = text.split('\n');
312
+ const clean = text.replace(/\[STRATEGY_READY\]/g, '').trim();
313
+ const lines = clean.split('\n');
285
314
  return lines.map((l, i) => i === 0 ? l : ' ' + l).join('\n');
286
315
  };
287
316
 
288
- /** My Strategies Menu */
289
- const myStrategiesMenu = async (strategies, service) => {
290
- while (true) {
291
- console.clear();
292
- displayBanner();
293
-
294
- const boxWidth = getLogoWidth();
295
- const W = boxWidth - 2;
296
-
297
- console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
298
- console.log(chalk.cyan('║') + chalk.yellow.bold(centerText('MY STRATEGIES', W)) + chalk.cyan('║'));
299
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
300
-
301
- if (strategies.length === 0) {
302
- console.log(chalk.cyan('║') + chalk.gray(centerText('No strategies yet', W)) + chalk.cyan('║'));
303
- } else {
304
- for (let i = 0; i < strategies.length; i++) {
305
- const s = strategies[i];
306
- const num = `[${i + 1}]`.padEnd(4);
307
- const sname = (s.name || s.folder).substring(0, 30).padEnd(32);
308
- const modules = s.modules ? `${s.modules.length} modules` : '';
309
- const line = `${num} ${sname} ${chalk.gray(modules)}`;
310
- console.log(chalk.cyan('║') + ' ' + chalk.white(num) + chalk.cyan(sname) + chalk.gray(modules.padEnd(W - 38)) + chalk.cyan('║'));
311
- }
312
- }
313
-
314
- console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
315
- console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
316
- console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
317
-
318
- if (strategies.length === 0) {
319
- await prompts.waitForEnter();
320
- return;
321
- }
322
-
323
- const input = await prompts.textInput(chalk.cyan(`SELECT (1-${strategies.length}/B): `));
324
- const choice = (input || '').toLowerCase().trim();
325
-
326
- if (choice === 'b' || choice === '') return;
327
-
328
- const num = parseInt(choice);
329
- if (!isNaN(num) && num >= 1 && num <= strategies.length) {
330
- await strategyDetailMenu(strategies[num - 1], service);
331
- strategies.length = 0;
332
- strategies.push(...loadStrategies());
333
- }
334
- }
335
- };
336
-
337
- /** Strategy Detail Menu */
338
- const strategyDetailMenu = async (strategy, service) => {
339
- console.clear();
340
- displayBanner();
341
-
342
- const boxWidth = getLogoWidth();
343
- const W = boxWidth - 2;
344
-
345
- console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
346
- console.log(chalk.cyan('║') + chalk.green.bold(centerText((strategy.name || strategy.folder).toUpperCase(), W)) + chalk.cyan('║'));
347
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
348
-
349
- // Show modules
350
- const files = fs.readdirSync(strategy.path);
351
- console.log(chalk.cyan('║') + chalk.gray(centerText(`Path: ${strategy.path}`, W)) + chalk.cyan('║'));
352
- console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
353
- console.log(chalk.cyan('║') + chalk.white(centerText('MODULES:', W)) + chalk.cyan('║'));
317
+ /** Save strategy to disk */
318
+ const saveStrategy = async (strategyConfig, config) => {
319
+ ensureStrategiesDir();
354
320
 
355
- for (const f of files) {
356
- const icon = f.endsWith('.js') ? '📄' : f.endsWith('.json') ? '⚙️' : '📁';
357
- console.log(chalk.cyan('║') + centerText(`${icon} ${f}`, W) + chalk.cyan('║'));
321
+ const name = await prompts.textInput(chalk.cyan(' Strategy name: '));
322
+ if (!name || !name.trim()) {
323
+ console.log(chalk.gray(' Save cancelled.'));
324
+ return;
358
325
  }
359
326
 
360
- console.log(chalk.cyan('╠' + '═'.repeat(W) + ''));
327
+ const folderName = name.trim().toLowerCase().replace(/[^a-z0-9]/g, '-');
328
+ const strategyPath = path.join(STRATEGIES_DIR, folderName);
361
329
 
362
- // Options: Run, Edit with AI, Delete
363
- const opts = ['[1] RUN', '[2] EDIT WITH AI', '[3] DELETE'];
364
- const optLine = opts.join(' ');
365
- console.log(chalk.cyan('║') + centerText(
366
- chalk.green(opts[0]) + ' ' + chalk.yellow(opts[1]) + ' ' + chalk.red(opts[2]), W
367
- ) + chalk.cyan('║'));
330
+ if (!fs.existsSync(strategyPath)) fs.mkdirSync(strategyPath, { recursive: true });
368
331
 
369
- console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
370
- console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
371
- console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
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
+ };
372
342
 
373
- const input = await prompts.textInput(chalk.cyan('SELECT (1/2/3/B): '));
374
- const choice = (input || '').toLowerCase().trim();
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;
375
350
 
376
- if (choice === '1') {
377
- console.log(chalk.yellow('\n Running custom strategy...'));
378
- console.log(chalk.gray(' This will use your connected accounts and market data.'));
379
- console.log(chalk.gray(' (Full execution coming soon)'));
380
- await prompts.waitForEnter();
381
- } else if (choice === '2') {
382
- console.log(chalk.yellow('\n Edit with AI coming soon...'));
383
- await prompts.waitForEnter();
384
- } else if (choice === '3') {
385
- const confirm = await prompts.confirmPrompt(`Delete "${strategy.name || strategy.folder}"?`, false);
386
- if (confirm) {
387
- deleteStrategy(strategy.path);
388
- console.log(chalk.green('\n ✓ Strategy deleted'));
389
- await prompts.waitForEnter();
390
- }
391
- }
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 []; }
392
377
  };
393
378
 
394
- module.exports = { customStrategyMenu, loadStrategies, createStrategyFolder, saveModule };
379
+ module.exports = { customStrategyMenu, loadStrategies };
@@ -5,6 +5,7 @@
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
 
@@ -26,33 +27,52 @@ const algoTradingMenu = async (service) => {
26
27
  const boxWidth = getLogoWidth();
27
28
  const W = boxWidth - 2;
28
29
 
30
+ // Check if AI agent is connected
31
+ const aiProvider = getActiveProvider();
32
+ const hasAI = !!aiProvider;
33
+
29
34
  // Draw menu rectangle
30
35
  console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
31
36
  console.log(chalk.cyan('║') + chalk.magenta.bold(centerText('ALGO-TRADING', W)) + chalk.cyan('║'));
32
37
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
33
38
 
34
- // 3 columns layout
39
+ // 2 or 3 columns layout based on AI availability
35
40
  const col1 = '[1] ONE ACCOUNT';
36
41
  const col2 = '[2] COPY TRADING';
37
- const col3 = '[3] CUSTOM STRATEGY';
38
- const colWidth = Math.floor(W / 3);
39
- const lastColWidth = W - 2 * colWidth;
40
-
41
- const pad1 = Math.floor((colWidth - col1.length) / 2);
42
- const pad2 = Math.floor((colWidth - col2.length) / 2);
43
- const pad3 = Math.floor((lastColWidth - col3.length) / 2);
42
+ const col3 = hasAI ? '[3] CUSTOM STRATEGY' : '';
44
43
 
45
- const col1Str = ' '.repeat(pad1) + chalk.cyan(col1) + ' '.repeat(colWidth - col1.length - pad1);
46
- const col2Str = ' '.repeat(pad2) + chalk.yellow(col2) + ' '.repeat(colWidth - col2.length - pad2);
47
- const col3Str = ' '.repeat(pad3) + chalk.green(col3) + ' '.repeat(lastColWidth - col3.length - pad3);
48
-
49
- console.log(chalk.cyan('║') + col1Str + col2Str + col3Str + chalk.cyan('║'));
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
+ }
50
69
 
51
70
  console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
52
71
  console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
53
72
  console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
54
73
 
55
- const input = await prompts.textInput(chalk.cyan('SELECT (1/2/3/B): '));
74
+ const promptText = hasAI ? 'SELECT (1/2/3/B): ' : 'SELECT (1/2/B): ';
75
+ const input = await prompts.textInput(chalk.cyan(promptText));
56
76
  const choice = (input || '').toLowerCase().trim();
57
77
 
58
78
  log.debug('Algo mode selected', { choice });
@@ -71,8 +91,13 @@ const algoTradingMenu = async (service) => {
71
91
  await copyTradingMenu();
72
92
  break;
73
93
  case '3':
74
- log.info('Starting Custom Strategy mode');
75
- await customStrategyMenu(service);
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
+ }
76
101
  break;
77
102
  default:
78
103
  console.log(chalk.red(' INVALID OPTION'));