hedgequantx 2.7.89 → 2.7.91

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.89",
3
+ "version": "2.7.91",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/app.js CHANGED
@@ -133,7 +133,7 @@ const banner = async () => {
133
133
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
134
134
 
135
135
  const tagline = isMobile ? `HQX V${version}` : `PROP FUTURES ALGO TRADING V${version}`;
136
- console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
136
+ console.log(chalk.cyan('║') + chalk.yellow(centerText(tagline, innerWidth)) + chalk.cyan('║'));
137
137
 
138
138
  // ALWAYS close the banner
139
139
  console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Algo Executor - Shared execution engine for all algo modes
3
+ * Handles market data, signals, orders, and P&L tracking
4
+ */
5
+
6
+ const readline = require('readline');
7
+ const { AlgoUI, renderSessionSummary } = require('./ui');
8
+ const { M1 } = require('../../lib/m/s1');
9
+ const { MarketDataFeed } = require('../../lib/data');
10
+
11
+ /**
12
+ * Execute algo strategy with market data
13
+ * @param {Object} params - Execution parameters
14
+ * @param {Object} params.service - Trading service (Rithmic/ProjectX)
15
+ * @param {Object} params.account - Account object
16
+ * @param {Object} params.contract - Contract object
17
+ * @param {Object} params.config - Algo config (contracts, target, risk, showName)
18
+ * @param {Object} params.options - Optional: aiSupervision, aiProvider, askAI function
19
+ */
20
+ const executeAlgo = async ({ service, account, contract, config, options = {} }) => {
21
+ const { contracts, dailyTarget, maxRisk, showName } = config;
22
+ const { aiSupervision, aiProvider, askAI, subtitle } = options;
23
+
24
+ const accountName = showName
25
+ ? (account.accountName || account.rithmicAccountId || account.accountId)
26
+ : 'HQX *****';
27
+ const symbolName = contract.name;
28
+ const contractId = contract.id;
29
+ const tickSize = contract.tickSize || 0.25;
30
+
31
+ const ui = new AlgoUI({
32
+ subtitle: subtitle || (aiSupervision ? 'CUSTOM STRATEGY + AI' : 'HQX Ultra Scalping'),
33
+ mode: 'one-account'
34
+ });
35
+
36
+ const stats = {
37
+ accountName,
38
+ symbol: symbolName,
39
+ qty: contracts,
40
+ target: dailyTarget,
41
+ risk: maxRisk,
42
+ propfirm: account.propfirm || 'Unknown',
43
+ platform: account.platform || 'Rithmic',
44
+ pnl: 0,
45
+ trades: 0,
46
+ wins: 0,
47
+ losses: 0,
48
+ latency: 0,
49
+ connected: false,
50
+ startTime: Date.now()
51
+ };
52
+
53
+ let running = true;
54
+ let stopReason = null;
55
+ let startingPnL = null;
56
+ let currentPosition = 0;
57
+ let pendingOrder = false;
58
+ let tickCount = 0;
59
+
60
+ // AI context for supervision
61
+ const aiContext = { recentTicks: [], recentSignals: [], maxTicks: 100 };
62
+
63
+ // Initialize Strategy
64
+ const strategy = new M1({ tickSize });
65
+ strategy.initialize(contractId, tickSize);
66
+
67
+ // Initialize Market Data Feed
68
+ const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
69
+
70
+ // Log startup
71
+ ui.addLog('info', `Strategy: ${aiSupervision ? 'CUSTOM + AI' : 'HQX Ultra Scalping'}`);
72
+ ui.addLog('info', `Account: ${accountName}`);
73
+ ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
74
+ ui.addLog('info', `Target: $${dailyTarget} | Risk: $${maxRisk}`);
75
+ if (aiSupervision && aiProvider) ui.addLog('info', `AI: ${aiProvider.name} supervision`);
76
+ ui.addLog('info', 'Connecting to market data...');
77
+
78
+ // Handle strategy signals
79
+ strategy.on('signal', async (signal) => {
80
+ if (!running || pendingOrder || currentPosition !== 0) return;
81
+
82
+ const { direction, entry, stopLoss, takeProfit, confidence } = signal;
83
+
84
+ aiContext.recentSignals.push({ ...signal, timestamp: Date.now() });
85
+ if (aiContext.recentSignals.length > 10) aiContext.recentSignals.shift();
86
+
87
+ ui.addLog('info', `Signal: ${direction.toUpperCase()} @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
88
+
89
+ // AI Supervision check
90
+ if (aiSupervision && askAI) {
91
+ const aiDecision = await askAI(aiContext, signal, { symbolName, currentPosition, stats, dailyTarget, maxRisk });
92
+ if (!aiDecision.approve) {
93
+ ui.addLog('info', `AI rejected: ${aiDecision.reason || 'No reason'}`);
94
+ return;
95
+ }
96
+ ui.addLog('info', `AI approved: ${aiDecision.reason || 'OK'}`);
97
+ }
98
+
99
+ // Place order
100
+ pendingOrder = true;
101
+ try {
102
+ const orderSide = direction === 'long' ? 0 : 1;
103
+ const orderResult = await service.placeOrder({
104
+ accountId: account.accountId,
105
+ contractId: contractId,
106
+ type: 2,
107
+ side: orderSide,
108
+ size: contracts
109
+ });
110
+
111
+ if (orderResult.success) {
112
+ currentPosition = direction === 'long' ? contracts : -contracts;
113
+ stats.trades++;
114
+ ui.addLog('fill_' + (direction === 'long' ? 'buy' : 'sell'),
115
+ `OPENED ${direction.toUpperCase()} ${contracts}x @ market`);
116
+
117
+ // Bracket orders
118
+ if (stopLoss && takeProfit) {
119
+ await service.placeOrder({
120
+ accountId: account.accountId, contractId, type: 4,
121
+ side: direction === 'long' ? 1 : 0, size: contracts, stopPrice: stopLoss
122
+ });
123
+ await service.placeOrder({
124
+ accountId: account.accountId, contractId, type: 1,
125
+ side: direction === 'long' ? 1 : 0, size: contracts, limitPrice: takeProfit
126
+ });
127
+ ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
128
+ }
129
+ } else {
130
+ ui.addLog('error', `Order failed: ${orderResult.error}`);
131
+ }
132
+ } catch (e) {
133
+ ui.addLog('error', `Order error: ${e.message}`);
134
+ }
135
+ pendingOrder = false;
136
+ });
137
+
138
+ // Handle market data ticks
139
+ marketFeed.on('tick', (tick) => {
140
+ tickCount++;
141
+ const latencyStart = Date.now();
142
+
143
+ aiContext.recentTicks.push(tick);
144
+ if (aiContext.recentTicks.length > aiContext.maxTicks) aiContext.recentTicks.shift();
145
+
146
+ strategy.processTick({
147
+ contractId: tick.contractId || contractId,
148
+ price: tick.price, bid: tick.bid, ask: tick.ask,
149
+ volume: tick.volume || 1, side: tick.lastTradeSide || 'unknown',
150
+ timestamp: tick.timestamp || Date.now()
151
+ });
152
+
153
+ stats.latency = Date.now() - latencyStart;
154
+ if (tickCount % 100 === 0) ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
155
+ });
156
+
157
+ marketFeed.on('connected', () => { stats.connected = true; ui.addLog('connected', 'Market data connected!'); });
158
+ marketFeed.on('error', (err) => ui.addLog('error', `Market: ${err.message}`));
159
+ marketFeed.on('disconnected', () => { stats.connected = false; ui.addLog('error', 'Market disconnected'); });
160
+
161
+ // Connect to market data
162
+ try {
163
+ const token = service.token || service.getToken?.();
164
+ const propfirmKey = (account.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
165
+ await marketFeed.connect(token, propfirmKey, contractId);
166
+ await marketFeed.subscribe(symbolName, contractId);
167
+ } catch (e) {
168
+ ui.addLog('error', `Failed to connect: ${e.message}`);
169
+ }
170
+
171
+ // Poll P&L
172
+ const pollPnL = async () => {
173
+ try {
174
+ const accountResult = await service.getTradingAccounts();
175
+ if (accountResult.success && accountResult.accounts) {
176
+ const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
177
+ if (acc && acc.profitAndLoss !== undefined) {
178
+ if (startingPnL === null) startingPnL = acc.profitAndLoss;
179
+ stats.pnl = acc.profitAndLoss - startingPnL;
180
+ if (stats.pnl !== 0) strategy.recordTradeResult(stats.pnl);
181
+ }
182
+ }
183
+
184
+ const posResult = await service.getPositions(account.accountId);
185
+ if (posResult.success && posResult.positions) {
186
+ const pos = posResult.positions.find(p => {
187
+ const sym = p.contractId || p.symbol || '';
188
+ return sym.includes(contract.name) || sym.includes(contractId);
189
+ });
190
+
191
+ if (pos && pos.quantity !== 0) {
192
+ currentPosition = pos.quantity;
193
+ const pnl = pos.profitAndLoss || 0;
194
+ if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
195
+ else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
196
+ } else {
197
+ currentPosition = 0;
198
+ }
199
+ }
200
+
201
+ if (stats.pnl >= dailyTarget) {
202
+ stopReason = 'target'; running = false;
203
+ ui.addLog('fill_win', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
204
+ } else if (stats.pnl <= -maxRisk) {
205
+ stopReason = 'risk'; running = false;
206
+ ui.addLog('fill_loss', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
207
+ }
208
+ } catch (e) { /* silent */ }
209
+ };
210
+
211
+ // Start loops
212
+ const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
213
+ const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
214
+ pollPnL();
215
+
216
+ // Keyboard handler
217
+ const setupKeyHandler = () => {
218
+ if (!process.stdin.isTTY) return;
219
+ readline.emitKeypressEvents(process.stdin);
220
+ process.stdin.setRawMode(true);
221
+ process.stdin.resume();
222
+
223
+ const onKey = (str, key) => {
224
+ if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
225
+ running = false; stopReason = 'manual';
226
+ }
227
+ };
228
+ process.stdin.on('keypress', onKey);
229
+ return () => {
230
+ process.stdin.removeListener('keypress', onKey);
231
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
232
+ };
233
+ };
234
+
235
+ const cleanupKeys = setupKeyHandler();
236
+
237
+ // Wait for stop
238
+ await new Promise(resolve => {
239
+ const check = setInterval(() => { if (!running) { clearInterval(check); resolve(); } }, 100);
240
+ });
241
+
242
+ // Cleanup
243
+ clearInterval(refreshInterval);
244
+ clearInterval(pnlInterval);
245
+ await marketFeed.disconnect();
246
+ if (cleanupKeys) cleanupKeys();
247
+ ui.cleanup();
248
+
249
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
250
+ process.stdin.resume();
251
+
252
+ // Duration
253
+ const durationMs = Date.now() - stats.startTime;
254
+ const hours = Math.floor(durationMs / 3600000);
255
+ const minutes = Math.floor((durationMs % 3600000) / 60000);
256
+ const seconds = Math.floor((durationMs % 60000) / 1000);
257
+ stats.duration = hours > 0 ? `${hours}h ${minutes}m ${seconds}s` : minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
258
+
259
+ renderSessionSummary(stats, stopReason);
260
+
261
+ console.log('\n Returning to menu in 3 seconds...');
262
+ await new Promise(resolve => setTimeout(resolve, 3000));
263
+ };
264
+
265
+ module.exports = { executeAlgo };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Custom Strategy - AI-powered strategy builder
3
- * Same config flow as One Account, then AI chat to create strategy
3
+ * Config flow + AI chat to create strategy, then execute with AI supervision
4
4
  */
5
5
 
6
6
  const chalk = require('chalk');
@@ -8,7 +8,6 @@ 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');
12
11
 
13
12
  const { getLogoWidth, centerText, displayBanner } = require('../../ui');
14
13
  const { prompts } = require('../../utils');
@@ -16,18 +15,16 @@ const { connections } = require('../../services');
16
15
  const { getActiveProvider } = require('../ai-agents');
17
16
  const cliproxy = require('../../services/cliproxy');
18
17
  const { checkMarketHours } = require('../../services/rithmic/market');
18
+ const { executeAlgo } = require('./algo-executor');
19
19
 
20
- // Strategies directory
21
20
  const STRATEGIES_DIR = path.join(os.homedir(), '.hqx', 'strategies');
22
21
 
23
- /** Ensure strategies directory exists */
24
22
  const ensureStrategiesDir = () => {
25
23
  if (!fs.existsSync(STRATEGIES_DIR)) fs.mkdirSync(STRATEGIES_DIR, { recursive: true });
26
24
  };
27
25
 
28
26
  /** Custom Strategy Menu */
29
27
  const customStrategyMenu = async (service) => {
30
- // Check AI provider first
31
28
  const aiProvider = getActiveProvider();
32
29
  if (!aiProvider) {
33
30
  console.log(chalk.red('\n No AI Agent connected. Go to AI Agents menu first.'));
@@ -35,47 +32,30 @@ const customStrategyMenu = async (service) => {
35
32
  return;
36
33
  }
37
34
 
38
- // Check market hours
39
35
  const market = checkMarketHours();
40
36
  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();
37
+ console.log(chalk.red(`\n ${market.message}`));
38
+ console.log(chalk.gray(' Custom strategy requires market to be open\n'));
45
39
  await prompts.waitForEnter();
46
40
  return;
47
41
  }
48
42
 
49
- // Step 1: Fetch accounts
50
43
  const spinner = ora({ text: 'Fetching active accounts...', color: 'yellow' }).start();
51
44
  const allAccounts = await connections.getAllAccounts();
52
45
 
53
- if (!allAccounts?.length) {
54
- spinner.fail('No accounts found');
55
- await prompts.waitForEnter();
56
- return;
57
- }
46
+ if (!allAccounts?.length) { spinner.fail('No accounts found'); await prompts.waitForEnter(); return; }
58
47
 
59
48
  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
- }
49
+ if (!activeAccounts.length) { spinner.fail('No active accounts'); await prompts.waitForEnter(); return; }
65
50
 
66
51
  spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
67
52
 
68
- // Step 2: Select account
69
- console.log();
70
- console.log(chalk.cyan.bold(' STEP 1: SELECT ACCOUNT'));
53
+ // Step 1: Select account
54
+ console.log(chalk.cyan.bold('\n STEP 1: SELECT ACCOUNT'));
71
55
  const accountOptions = activeAccounts.map(acc => {
72
56
  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
- };
57
+ const balance = acc.balance !== null && acc.balance !== undefined ? ` - $${acc.balance.toLocaleString()}` : '';
58
+ return { label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`, value: acc };
79
59
  });
80
60
  accountOptions.push({ label: '< Back', value: 'back' });
81
61
 
@@ -84,16 +64,13 @@ const customStrategyMenu = async (service) => {
84
64
 
85
65
  const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
86
66
 
87
- // Step 3: Select symbol
88
- console.log();
89
- console.log(chalk.cyan.bold(' STEP 2: SELECT SYMBOL'));
67
+ // Step 2: Select symbol
68
+ console.log(chalk.cyan.bold('\n STEP 2: SELECT SYMBOL'));
90
69
  const contract = await selectSymbol(accountService);
91
70
  if (!contract) return;
92
71
 
93
- // Step 4: Configure parameters
94
- console.log();
95
- console.log(chalk.cyan.bold(' STEP 3: CONFIGURE PARAMETERS'));
96
- console.log();
72
+ // Step 3: Configure parameters
73
+ console.log(chalk.cyan.bold('\n STEP 3: CONFIGURE PARAMETERS\n'));
97
74
 
98
75
  const contracts = await prompts.numberInput('Number of contracts:', 1, 1, 10);
99
76
  if (contracts === null) return;
@@ -107,50 +84,30 @@ const customStrategyMenu = async (service) => {
107
84
  const showName = await prompts.confirmPrompt('Show account name?', false);
108
85
  if (showName === null) return;
109
86
 
110
- // Step 5: AI Supervision
111
- console.log();
112
- console.log(chalk.cyan.bold(' STEP 4: AI SUPERVISION'));
87
+ // Step 4: AI Supervision
88
+ console.log(chalk.cyan.bold('\n STEP 4: AI SUPERVISION'));
113
89
  const aiSupervision = await prompts.confirmPrompt('Enable AI supervision during execution?', true);
114
90
  if (aiSupervision === null) return;
115
91
 
116
- const config = {
117
- account: selectedAccount,
118
- contract,
119
- contracts,
120
- dailyTarget,
121
- maxRisk,
122
- showName,
123
- aiSupervision,
124
- aiProvider
125
- };
92
+ const config = { account: selectedAccount, contract, contracts, dailyTarget, maxRisk, showName, aiSupervision, aiProvider };
126
93
 
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();
94
+ // Step 5: AI Chat
95
+ console.log(chalk.cyan.bold('\n STEP 5: CREATE YOUR STRATEGY WITH AI'));
96
+ console.log(chalk.gray(' Describe your trading strategy. AI will help build it.\n'));
133
97
 
134
98
  await strategyChat(config, accountService);
135
99
  };
136
100
 
137
- /** Select symbol - same as one-account */
101
+ /** Select symbol */
138
102
  const selectSymbol = async (service) => {
139
103
  const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
140
104
 
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;
105
+ const result = await service.getContracts();
106
+ if (!result.success || !result.contracts?.length) { spinner.fail('Failed to load contracts'); return null; }
148
107
 
149
- // Sort popular indices first
150
108
  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 || '';
109
+ result.contracts.sort((a, b) => {
110
+ const baseA = a.baseSymbol || a.symbol || '', baseB = b.baseSymbol || b.symbol || '';
154
111
  const idxA = popular.findIndex(p => baseA === p || baseA.startsWith(p));
155
112
  const idxB = popular.findIndex(p => baseB === p || baseB.startsWith(p));
156
113
  if (idxA !== -1 && idxB !== -1) return idxA - idxB;
@@ -159,12 +116,9 @@ const selectSymbol = async (service) => {
159
116
  return baseA.localeCompare(baseB);
160
117
  });
161
118
 
162
- spinner.succeed(`Found ${contracts.length} contracts`);
119
+ spinner.succeed(`Found ${result.contracts.length} contracts`);
163
120
 
164
- const options = contracts.map(c => ({
165
- label: `${c.symbol} - ${c.name} (${c.exchange})`,
166
- value: c
167
- }));
121
+ const options = result.contracts.map(c => ({ label: `${c.symbol} - ${c.name} (${c.exchange})`, value: c }));
168
122
  options.push({ label: chalk.gray('< Back'), value: 'back' });
169
123
 
170
124
  const selected = await prompts.selectOption(chalk.yellow('Select Symbol:'), options);
@@ -174,75 +128,45 @@ const selectSymbol = async (service) => {
174
128
  /** AI Chat for strategy creation */
175
129
  const strategyChat = async (config, service) => {
176
130
  const { account, contract, contracts, dailyTarget, maxRisk, showName, aiSupervision, aiProvider } = config;
177
-
178
- const accountName = showName
179
- ? (account.accountName || account.rithmicAccountId || account.accountId)
180
- : 'HQX *****';
131
+ const accountName = showName ? (account.accountName || account.rithmicAccountId || account.accountId) : 'HQX *****';
181
132
 
182
133
  console.clear();
183
134
  displayBanner();
184
135
 
185
- const boxWidth = getLogoWidth();
186
- const W = boxWidth - 2;
187
-
136
+ const W = getLogoWidth() - 2;
188
137
  console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
189
138
  console.log(chalk.cyan('║') + chalk.green.bold(centerText('CUSTOM STRATEGY - AI CHAT', W)) + chalk.cyan('║'));
190
139
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
191
140
  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('║'));
141
+ console.log(chalk.cyan('║') + centerText(`Target: $${dailyTarget} | Risk: $${maxRisk} | AI: ${aiSupervision ? 'ON' : 'OFF'}`, W) + chalk.cyan('║'));
142
+ console.log(chalk.cyan('║') + chalk.gray(centerText(`Provider: ${aiProvider.name}`, W)) + chalk.cyan('║'));
194
143
  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();
144
+ console.log(chalk.cyan('║') + chalk.gray(centerText('"run" to execute, "save" to save, "cancel" to abort', W)) + chalk.cyan('║'));
145
+ console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝\n'));
198
146
 
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.`;
147
+ const systemPrompt = `You are an expert algorithmic trading assistant.
148
+ Setup: ${accountName} | ${contract.name} | ${contracts} contracts | Target: $${dailyTarget} | Risk: $${maxRisk}
149
+ Help create a trading strategy. Be concise (2-3 sentences). When ready, say [STRATEGY_READY] with JSON config.`;
218
150
 
219
151
  const messages = [{ role: 'system', content: systemPrompt }];
220
- let strategyReady = false;
221
- let strategyConfig = null;
152
+ let strategyReady = false, strategyConfig = null;
222
153
 
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?');
154
+ console.log(chalk.green(' AI: ') + `I'll help you create a custom strategy for ${contract.name}. What kind of strategy?`);
225
155
  console.log();
226
156
 
227
157
  while (true) {
228
158
  const userInput = await prompts.textInput(chalk.yellow(' You: '));
229
-
230
159
  if (!userInput) continue;
231
160
 
232
161
  const cmd = userInput.toLowerCase().trim();
233
162
 
234
163
  if (cmd === 'cancel' || cmd === 'exit' || cmd === 'quit') {
235
- console.log(chalk.gray('\n Strategy creation cancelled.'));
236
- await prompts.waitForEnter();
237
- return;
164
+ console.log(chalk.gray('\n Cancelled.')); await prompts.waitForEnter(); return;
238
165
  }
239
166
 
240
167
  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
- }
168
+ if (strategyConfig) await saveStrategy(strategyConfig, config);
169
+ else console.log(chalk.yellow('\n No strategy to save yet.'));
246
170
  continue;
247
171
  }
248
172
 
@@ -251,114 +175,101 @@ Include the strategy parameters in JSON format when ready.`;
251
175
  console.log(chalk.green('\n Launching strategy...'));
252
176
  await launchCustomStrategy(config, strategyConfig, service);
253
177
  return;
254
- } else {
255
- console.log(chalk.yellow('\n Strategy not ready yet. Describe your entry/exit conditions first.'));
256
- continue;
257
178
  }
179
+ console.log(chalk.yellow('\n Strategy not ready. Describe your entry/exit conditions first.'));
180
+ continue;
258
181
  }
259
182
 
260
183
  messages.push({ role: 'user', content: userInput });
261
-
262
184
  const spinner = ora({ text: 'AI thinking...', color: 'yellow' }).start();
263
185
 
264
186
  try {
265
187
  const modelId = aiProvider.modelId || getDefaultModel(aiProvider.id);
266
188
  const result = await cliproxy.chatCompletion(modelId, messages);
267
189
 
268
- if (!result.success) {
269
- spinner.fail(`AI Error: ${result.error}`);
270
- messages.pop();
271
- continue;
272
- }
190
+ if (!result.success) { spinner.fail(`AI Error: ${result.error}`); messages.pop(); continue; }
273
191
 
274
192
  const response = result.response?.choices?.[0]?.message?.content || '';
275
193
  messages.push({ role: 'assistant', content: response });
276
-
277
194
  spinner.stop();
278
- console.log();
279
- console.log(chalk.green(' AI: ') + formatResponse(response));
280
- console.log();
281
195
 
282
- // Check if strategy is ready
196
+ console.log('\n' + chalk.green(' AI: ') + formatResponse(response) + '\n');
197
+
283
198
  if (response.includes('[STRATEGY_READY]') || response.toLowerCase().includes('strategy is ready')) {
284
199
  strategyReady = true;
285
- // Try to extract JSON config
286
200
  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();
201
+ if (jsonMatch) try { strategyConfig = JSON.parse(jsonMatch[0]); } catch (e) {}
202
+ if (!strategyConfig) strategyConfig = { description: userInput, messages: messages.slice(1) };
203
+ console.log(chalk.cyan(' [Strategy ready! "run" to execute or "save" to save]\n'));
295
204
  }
296
-
297
- } catch (e) {
298
- spinner.fail(`Error: ${e.message}`);
299
- messages.pop();
300
- }
205
+ } catch (e) { spinner.fail(`Error: ${e.message}`); messages.pop(); }
301
206
  }
302
207
  };
303
208
 
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
- };
209
+ const getDefaultModel = (id) => ({ anthropic: 'claude-sonnet-4-20250514', google: 'gemini-2.5-pro', openai: 'gpt-4o' }[id] || 'claude-sonnet-4-20250514');
309
210
 
310
- /** Format AI response */
311
211
  const formatResponse = (text) => {
312
- const clean = text.replace(/\[STRATEGY_READY\]/g, '').trim();
313
- const lines = clean.split('\n');
212
+ const lines = text.replace(/\[STRATEGY_READY\]/g, '').trim().split('\n');
314
213
  return lines.map((l, i) => i === 0 ? l : ' ' + l).join('\n');
315
214
  };
316
215
 
317
- /** Save strategy to disk */
216
+ /** Save strategy */
318
217
  const saveStrategy = async (strategyConfig, config) => {
319
218
  ensureStrategiesDir();
320
-
321
219
  const name = await prompts.textInput(chalk.cyan(' Strategy name: '));
322
- if (!name || !name.trim()) {
323
- console.log(chalk.gray(' Save cancelled.'));
324
- return;
325
- }
220
+ if (!name?.trim()) { console.log(chalk.gray(' Save cancelled.')); return; }
326
221
 
327
222
  const folderName = name.trim().toLowerCase().replace(/[^a-z0-9]/g, '-');
328
223
  const strategyPath = path.join(STRATEGIES_DIR, folderName);
329
-
330
224
  if (!fs.existsSync(strategyPath)) fs.mkdirSync(strategyPath, { recursive: true });
331
225
 
332
226
  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()
227
+ name: name.trim(), symbol: config.contract.name, contracts: config.contracts,
228
+ dailyTarget: config.dailyTarget, maxRisk: config.maxRisk, aiSupervision: config.aiSupervision,
229
+ strategy: strategyConfig, createdAt: new Date().toISOString()
341
230
  };
342
231
 
343
232
  fs.writeFileSync(path.join(strategyPath, 'config.json'), JSON.stringify(configFile, null, 2));
344
- console.log(chalk.green(`\n ✓ Strategy saved: ${strategyPath}`));
233
+ console.log(chalk.green(`\n ✓ Saved: ${strategyPath}`));
345
234
  };
346
235
 
347
- /** Launch custom strategy execution */
236
+ /** Launch custom strategy with AI supervision */
348
237
  const launchCustomStrategy = async (config, strategyConfig, service) => {
349
238
  const { account, contract, contracts, dailyTarget, maxRisk, showName, aiSupervision, aiProvider } = config;
350
239
 
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();
240
+ // AI supervision function
241
+ const askAI = async (aiContext, signal, ctx) => {
242
+ if (!aiSupervision) return { approve: true };
243
+
244
+ const prompt = `Trading supervisor check:
245
+ Symbol: ${ctx.symbolName} | Position: ${ctx.currentPosition === 0 ? 'FLAT' : (ctx.currentPosition > 0 ? 'LONG' : 'SHORT')}
246
+ P&L: $${ctx.stats.pnl.toFixed(2)} | Trades: ${ctx.stats.trades} (W:${ctx.stats.wins} L:${ctx.stats.losses})
247
+ Strategy: ${JSON.stringify(strategyConfig.description || strategyConfig).substring(0, 200)}
248
+ Signal: ${signal.direction.toUpperCase()} @ ${signal.entry.toFixed(2)} (${(signal.confidence * 100).toFixed(0)}%)
249
+ Recent prices: ${aiContext.recentTicks.slice(-5).map(t => t.price?.toFixed(2)).join(', ') || 'N/A'}
250
+ Reply JSON: {"approve": true/false, "reason": "brief"}`;
251
+
252
+ try {
253
+ const modelId = aiProvider.modelId || getDefaultModel(aiProvider.id);
254
+ const result = await cliproxy.chatCompletion(modelId, [
255
+ { role: 'system', content: 'Trading supervisor. JSON only.' },
256
+ { role: 'user', content: prompt }
257
+ ]);
258
+
259
+ if (result.success) {
260
+ const content = result.response?.choices?.[0]?.message?.content || '';
261
+ const match = content.match(/\{[\s\S]*\}/);
262
+ if (match) return JSON.parse(match[0]);
263
+ }
264
+ } catch (e) { /* fallback */ }
265
+ return { approve: true, reason: 'AI unavailable' };
266
+ };
267
+
268
+ await executeAlgo({
269
+ service, account, contract,
270
+ config: { contracts, dailyTarget, maxRisk, showName },
271
+ options: { aiSupervision, aiProvider, askAI, subtitle: 'CUSTOM STRATEGY + AI' }
272
+ });
362
273
  };
363
274
 
364
275
  /** Load saved strategies */
@@ -368,9 +279,7 @@ const loadStrategies = () => {
368
279
  const items = fs.readdirSync(STRATEGIES_DIR, { withFileTypes: true });
369
280
  return items.filter(i => i.isDirectory()).map(dir => {
370
281
  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
- }
282
+ if (fs.existsSync(configPath)) return { folder: dir.name, ...JSON.parse(fs.readFileSync(configPath, 'utf8')) };
374
283
  return { folder: dir.name, name: dir.name };
375
284
  });
376
285
  } catch (e) { return []; }
@@ -4,16 +4,11 @@
4
4
 
5
5
  const chalk = require('chalk');
6
6
  const ora = require('ora');
7
- const readline = require('readline');
8
7
 
9
8
  const { connections } = require('../../services');
10
- const { AlgoUI, renderSessionSummary } = require('./ui');
11
9
  const { prompts } = require('../../utils');
12
10
  const { checkMarketHours } = require('../../services/rithmic/market');
13
-
14
- // Strategy & Market Data
15
- const { M1 } = require('../../lib/m/s1');
16
- const { MarketDataFeed } = require('../../lib/data');
11
+ const { executeAlgo } = require('./algo-executor');
17
12
 
18
13
 
19
14
 
@@ -80,7 +75,12 @@ const oneAccountMenu = async (service) => {
80
75
  const config = await configureAlgo(selectedAccount, contract);
81
76
  if (!config) return;
82
77
 
83
- await launchAlgo(accountService, selectedAccount, contract, config);
78
+ await executeAlgo({
79
+ service: accountService,
80
+ account: selectedAccount,
81
+ contract,
82
+ config
83
+ });
84
84
  };
85
85
 
86
86
  /**
@@ -158,286 +158,4 @@ const configureAlgo = async (account, contract) => {
158
158
  return { contracts, dailyTarget, maxRisk, showName };
159
159
  };
160
160
 
161
- /**
162
- * Launch algo trading - HQX Ultra Scalping Strategy
163
- * Real-time market data + Strategy signals + Auto order execution
164
- */
165
- const launchAlgo = async (service, account, contract, config) => {
166
- const { contracts, dailyTarget, maxRisk, showName } = config;
167
-
168
- // Use RAW API fields
169
- const accountName = showName
170
- ? (account.accountName || account.rithmicAccountId || account.accountId)
171
- : 'HQX *****';
172
- const symbolName = contract.name;
173
- const contractId = contract.id;
174
- const connectionType = account.platform || 'Rithmic';
175
- const tickSize = contract.tickSize || 0.25;
176
-
177
- const ui = new AlgoUI({ subtitle: 'HQX Ultra Scalping', mode: 'one-account' });
178
-
179
- const stats = {
180
- accountName,
181
- symbol: symbolName,
182
- qty: contracts,
183
- target: dailyTarget,
184
- risk: maxRisk,
185
- propfirm: account.propfirm || 'Unknown',
186
- platform: connectionType,
187
- pnl: 0,
188
- trades: 0,
189
- wins: 0,
190
- losses: 0,
191
- latency: 0,
192
- connected: false,
193
- startTime: Date.now()
194
- };
195
-
196
- let running = true;
197
- let stopReason = null;
198
- let startingPnL = null;
199
- let currentPosition = 0; // Current position qty (+ long, - short)
200
- let pendingOrder = false; // Prevent duplicate orders
201
- let tickCount = 0;
202
-
203
- // Initialize Strategy
204
- const strategy = new M1({ tickSize });
205
- strategy.initialize(contractId, tickSize);
206
-
207
- // Initialize Market Data Feed
208
- const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
209
-
210
- // Log startup
211
- ui.addLog('info', `Connection: ${connectionType}`);
212
- ui.addLog('info', `Account: ${accountName}`);
213
- ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
214
- ui.addLog('info', `Target: $${dailyTarget} | Max Risk: $${maxRisk}`);
215
- ui.addLog('info', 'Connecting to market data...');
216
-
217
- // Handle strategy signals
218
- strategy.on('signal', async (signal) => {
219
- if (!running || pendingOrder || currentPosition !== 0) return;
220
-
221
- const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
222
-
223
- ui.addLog('signal', `${direction.toUpperCase()} signal @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
224
-
225
- // Place order via API
226
- pendingOrder = true;
227
- try {
228
- const orderSide = direction === 'long' ? 0 : 1; // 0=Buy, 1=Sell
229
- const orderResult = await service.placeOrder({
230
- accountId: account.accountId,
231
- contractId: contractId,
232
- type: 2, // Market order
233
- side: orderSide,
234
- size: contracts
235
- });
236
-
237
- if (orderResult.success) {
238
- currentPosition = direction === 'long' ? contracts : -contracts;
239
- stats.trades++;
240
- ui.addLog('trade', `OPENED ${direction.toUpperCase()} ${contracts}x @ market`);
241
-
242
- // Place bracket orders (SL/TP)
243
- if (stopLoss && takeProfit) {
244
- // Stop Loss
245
- await service.placeOrder({
246
- accountId: account.accountId,
247
- contractId: contractId,
248
- type: 4, // Stop order
249
- side: direction === 'long' ? 1 : 0, // Opposite side
250
- size: contracts,
251
- stopPrice: stopLoss
252
- });
253
-
254
- // Take Profit
255
- await service.placeOrder({
256
- accountId: account.accountId,
257
- contractId: contractId,
258
- type: 1, // Limit order
259
- side: direction === 'long' ? 1 : 0,
260
- size: contracts,
261
- limitPrice: takeProfit
262
- });
263
-
264
- ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
265
- }
266
- } else {
267
- ui.addLog('error', `Order failed: ${orderResult.error}`);
268
- }
269
- } catch (e) {
270
- ui.addLog('error', `Order error: ${e.message}`);
271
- }
272
- pendingOrder = false;
273
- });
274
-
275
- // Handle market data ticks
276
- marketFeed.on('tick', (tick) => {
277
- tickCount++;
278
- const latencyStart = Date.now();
279
-
280
- // Feed tick to strategy
281
- strategy.processTick({
282
- contractId: tick.contractId || contractId,
283
- price: tick.price,
284
- bid: tick.bid,
285
- ask: tick.ask,
286
- volume: tick.volume || 1,
287
- side: tick.lastTradeSide || 'unknown',
288
- timestamp: tick.timestamp || Date.now()
289
- });
290
-
291
- stats.latency = Date.now() - latencyStart;
292
-
293
- // Log every 100th tick to show activity
294
- if (tickCount % 100 === 0) {
295
- ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
296
- }
297
- });
298
-
299
- marketFeed.on('connected', () => {
300
- stats.connected = true;
301
- ui.addLog('success', 'Market data connected!');
302
- });
303
-
304
- marketFeed.on('error', (err) => {
305
- ui.addLog('error', `Market: ${err.message}`);
306
- });
307
-
308
- marketFeed.on('disconnected', () => {
309
- stats.connected = false;
310
- ui.addLog('error', 'Market data disconnected');
311
- });
312
-
313
- // Connect to market data
314
- try {
315
- const token = service.token || service.getToken?.();
316
- const propfirmKey = (account.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
317
- await marketFeed.connect(token, propfirmKey, contractId);
318
- await marketFeed.subscribe(symbolName, contractId);
319
- } catch (e) {
320
- ui.addLog('error', `Failed to connect: ${e.message}`);
321
- }
322
-
323
- // Poll account P&L from API
324
- const pollPnL = async () => {
325
- try {
326
- const accountResult = await service.getTradingAccounts();
327
- if (accountResult.success && accountResult.accounts) {
328
- const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
329
- if (acc && acc.profitAndLoss !== undefined) {
330
- if (startingPnL === null) startingPnL = acc.profitAndLoss;
331
- stats.pnl = acc.profitAndLoss - startingPnL;
332
-
333
- // Record trade result in strategy
334
- if (stats.pnl !== 0) {
335
- strategy.recordTradeResult(stats.pnl);
336
- }
337
- }
338
- }
339
-
340
- // Check positions
341
- const posResult = await service.getPositions(account.accountId);
342
- if (posResult.success && posResult.positions) {
343
- const pos = posResult.positions.find(p => {
344
- const sym = p.contractId || p.symbol || '';
345
- return sym.includes(contract.name) || sym.includes(contractId);
346
- });
347
-
348
- if (pos && pos.quantity !== 0) {
349
- currentPosition = pos.quantity;
350
- const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
351
- const pnl = pos.profitAndLoss || 0;
352
-
353
- // Check if position closed (win/loss)
354
- if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
355
- else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
356
- } else {
357
- currentPosition = 0;
358
- }
359
- }
360
-
361
- // Check target/risk
362
- if (stats.pnl >= dailyTarget) {
363
- stopReason = 'target';
364
- running = false;
365
- ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
366
- } else if (stats.pnl <= -maxRisk) {
367
- stopReason = 'risk';
368
- running = false;
369
- ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
370
- }
371
- } catch (e) {
372
- // Silently handle polling errors
373
- }
374
- };
375
-
376
- // Start polling and UI refresh
377
- const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
378
- const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
379
- pollPnL(); // Initial poll
380
-
381
- // Keyboard handler
382
- const setupKeyHandler = () => {
383
- if (!process.stdin.isTTY) return;
384
- readline.emitKeypressEvents(process.stdin);
385
- process.stdin.setRawMode(true);
386
- process.stdin.resume();
387
-
388
- const onKey = (str, key) => {
389
- if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
390
- running = false;
391
- stopReason = 'manual';
392
- }
393
- };
394
- process.stdin.on('keypress', onKey);
395
- return () => {
396
- process.stdin.removeListener('keypress', onKey);
397
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
398
- };
399
- };
400
-
401
- const cleanupKeys = setupKeyHandler();
402
-
403
- // Wait for stop
404
- await new Promise(resolve => {
405
- const check = setInterval(() => {
406
- if (!running) {
407
- clearInterval(check);
408
- resolve();
409
- }
410
- }, 100);
411
- });
412
-
413
- // Cleanup
414
- clearInterval(refreshInterval);
415
- clearInterval(pnlInterval);
416
- await marketFeed.disconnect();
417
- if (cleanupKeys) cleanupKeys();
418
- ui.cleanup();
419
-
420
- if (process.stdin.isTTY) {
421
- process.stdin.setRawMode(false);
422
- }
423
- process.stdin.resume();
424
-
425
- // Duration
426
- const durationMs = Date.now() - stats.startTime;
427
- const hours = Math.floor(durationMs / 3600000);
428
- const minutes = Math.floor((durationMs % 3600000) / 60000);
429
- const seconds = Math.floor((durationMs % 60000) / 1000);
430
- stats.duration = hours > 0
431
- ? `${hours}h ${minutes}m ${seconds}s`
432
- : minutes > 0
433
- ? `${minutes}m ${seconds}s`
434
- : `${seconds}s`;
435
-
436
- // Summary
437
- renderSessionSummary(stats, stopReason);
438
-
439
- console.log('\n Returning to menu in 3 seconds...');
440
- await new Promise(resolve => setTimeout(resolve, 3000));
441
- };
442
-
443
161
  module.exports = { oneAccountMenu };
@@ -127,7 +127,7 @@ class AlgoUI {
127
127
 
128
128
  // Separator + title
129
129
  this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
130
- this._line(chalk.cyan(BOX.V) + chalk.white(center(`PROP FUTURES ALGO TRADING V${version}`, W)) + chalk.cyan(BOX.V));
130
+ this._line(chalk.cyan(BOX.V) + chalk.yellow(center(`PROP FUTURES ALGO TRADING V${version}`, W)) + chalk.cyan(BOX.V));
131
131
  this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
132
132
  this._line(chalk.cyan(BOX.V) + chalk.yellow(center((this.config.subtitle || 'HQX ALGO TRADING').toUpperCase(), W)) + chalk.cyan(BOX.V));
133
133
  }
@@ -387,7 +387,7 @@ const renderSessionSummary = (stats, stopReason) => {
387
387
 
388
388
  // Separator + title
389
389
  console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
390
- console.log(chalk.cyan(BOX.V) + chalk.white(center(`PROP FUTURES ALGO TRADING V${version}`, W)) + chalk.cyan(BOX.V));
390
+ console.log(chalk.cyan(BOX.V) + chalk.yellow(center(`PROP FUTURES ALGO TRADING V${version}`, W)) + chalk.cyan(BOX.V));
391
391
  console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
392
392
  console.log(chalk.cyan(BOX.V) + chalk.yellow(center('SESSION SUMMARY', W)) + chalk.cyan(BOX.V));
393
393
 
package/src/ui/menu.js CHANGED
@@ -48,7 +48,7 @@ const createBoxMenu = async (title, items, options = {}) => {
48
48
  });
49
49
 
50
50
  console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
51
- console.log(chalk.cyan('║') + chalk.white(centerText(`PROP FUTURES ALGO TRADING V${version}`, innerWidth)) + chalk.cyan('║'));
51
+ console.log(chalk.cyan('║') + chalk.yellow(centerText(`PROP FUTURES ALGO TRADING V${version}`, innerWidth)) + chalk.cyan('║'));
52
52
 
53
53
  // Stats bar if provided
54
54
  if (options.statsLine) {