hedgequantx 2.7.87 → 2.7.88

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.87",
3
+ "version": "2.7.88",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Custom Strategy - AI-powered modular strategy builder
3
+ * Each strategy is a folder with modular components
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
+
12
+ const { getLogoWidth, centerText, displayBanner } = require('../../ui');
13
+ const { prompts } = require('../../utils');
14
+ const { getActiveProvider } = require('../ai-agents');
15
+ const cliproxy = require('../../services/cliproxy');
16
+
17
+ // Base strategies directory
18
+ const STRATEGIES_DIR = path.join(os.homedir(), '.hqx', 'strategies');
19
+
20
+ /** Ensure strategies directory exists */
21
+ const ensureStrategiesDir = () => {
22
+ if (!fs.existsSync(STRATEGIES_DIR)) fs.mkdirSync(STRATEGIES_DIR, { recursive: true });
23
+ };
24
+
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
+ /** Custom Strategy Menu */
72
+ 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
+ }
126
+ }
127
+ };
128
+
129
+ /** AI Wizard to create modular strategy */
130
+ const createStrategyWizard = async (aiProvider) => {
131
+ console.clear();
132
+ displayBanner();
133
+
134
+ const boxWidth = getLogoWidth();
135
+ const W = boxWidth - 2;
136
+
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();
141
+
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'));
147
+ await prompts.waitForEnter();
148
+ return;
149
+ }
150
+
151
+ // Create folder
152
+ const folder = createStrategyFolder(name.trim());
153
+ if (!folder.success) {
154
+ console.log(chalk.red(` Error: ${folder.error}`));
155
+ await prompts.waitForEnter();
156
+ return;
157
+ }
158
+
159
+ console.log(chalk.green(` ✓ Created: ${folder.path}`));
160
+ console.log();
161
+
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.'));
167
+ console.log();
168
+
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:
171
+
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
176
+
177
+ Ask clarifying questions. Be concise. When ready, output each module.
178
+
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
+ \`\`\`
186
+
187
+ The 'data' object contains: { price, bid, ask, volume, atr, ema20, ema50, rsi, macd, vwap, high, low, open, close }`;
188
+
189
+ const messages = [{ role: 'system', content: systemPrompt }];
190
+ const modules = {};
191
+
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"'));
194
+ console.log();
195
+
196
+ while (true) {
197
+ const userInput = await prompts.textInput(chalk.yellow(' You: '));
198
+
199
+ if (!userInput) continue;
200
+
201
+ if (userInput.toLowerCase() === 'cancel') {
202
+ deleteStrategy(folder.path);
203
+ console.log(chalk.gray('\n Strategy cancelled and folder deleted.'));
204
+ await prompts.waitForEnter();
205
+ return;
206
+ }
207
+
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}`));
222
+ }
223
+ await prompts.waitForEnter();
224
+ return;
225
+ }
226
+
227
+ messages.push({ role: 'user', content: userInput });
228
+
229
+ const spinner = ora({ text: 'AI thinking...', color: 'yellow' }).start();
230
+
231
+ try {
232
+ const modelId = aiProvider.modelId || getDefaultModel(aiProvider.id);
233
+ const result = await cliproxy.chatCompletion(modelId, messages);
234
+
235
+ if (!result.success) {
236
+ spinner.fail(`AI Error: ${result.error}`);
237
+ messages.pop();
238
+ continue;
239
+ }
240
+
241
+ const response = result.response?.choices?.[0]?.message?.content || '';
242
+ messages.push({ role: 'assistant', content: response });
243
+
244
+ spinner.stop();
245
+ console.log();
246
+
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}`));
254
+ }
255
+
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
+ } catch (e) {
266
+ spinner.fail(`Error: ${e.message}`);
267
+ messages.pop();
268
+ }
269
+ }
270
+ };
271
+
272
+ /** Get default model */
273
+ const getDefaultModel = (providerId) => {
274
+ const defaults = {
275
+ anthropic: 'claude-sonnet-4-20250514',
276
+ google: 'gemini-2.5-pro',
277
+ openai: 'gpt-4o'
278
+ };
279
+ return defaults[providerId] || 'claude-sonnet-4-20250514';
280
+ };
281
+
282
+ /** Format response for terminal */
283
+ const formatResponse = (text) => {
284
+ const lines = text.split('\n');
285
+ return lines.map((l, i) => i === 0 ? l : ' ' + l).join('\n');
286
+ };
287
+
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('║'));
354
+
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('║'));
358
+ }
359
+
360
+ console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
361
+
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('║'));
368
+
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) + '╝'));
372
+
373
+ const input = await prompts.textInput(chalk.cyan('SELECT (1/2/3/B): '));
374
+ const choice = (input || '').toLowerCase().trim();
375
+
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
+ }
392
+ };
393
+
394
+ module.exports = { customStrategyMenu, loadStrategies, createStrategyFolder, saveModule };
@@ -10,6 +10,7 @@ const log = logger.scope('AlgoMenu');
10
10
 
11
11
  const { oneAccountMenu } = require('./one-account');
12
12
  const { copyTradingMenu } = require('./copy-trading');
13
+ const { customStrategyMenu } = require('./custom-strategy');
13
14
 
14
15
  /**
15
16
  * Algo Trading Menu
@@ -87,27 +88,4 @@ const algoTradingMenu = async (service) => {
87
88
  }
88
89
  };
89
90
 
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
91
  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.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
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) + '╝'));