hedgequantx 2.9.19 → 2.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/app.js +42 -64
- package/src/lib/m/hqx-2b.js +7 -0
- package/src/lib/m/index.js +138 -0
- package/src/lib/m/ultra-scalping.js +7 -0
- package/src/menus/connect.js +14 -17
- package/src/menus/dashboard.js +58 -76
- package/src/pages/accounts.js +38 -49
- package/src/pages/algo/copy-trading.js +546 -178
- package/src/pages/algo/index.js +18 -75
- package/src/pages/algo/one-account.js +322 -57
- package/src/pages/algo/ui.js +15 -15
- package/src/pages/orders.js +19 -22
- package/src/pages/positions.js +19 -22
- package/src/pages/stats/index.js +15 -16
- package/src/pages/user.js +7 -11
- package/src/services/ai-supervision/health.js +35 -47
- package/src/services/index.js +1 -9
- package/src/services/rithmic/accounts.js +8 -6
- package/src/ui/box.js +9 -5
- package/src/ui/index.js +5 -18
- package/src/ui/menu.js +4 -4
- package/src/pages/ai-agents-ui.js +0 -388
- package/src/pages/ai-agents.js +0 -494
- package/src/pages/ai-models.js +0 -389
- package/src/pages/algo/algo-executor.js +0 -307
- package/src/pages/algo/copy-executor.js +0 -331
- package/src/pages/algo/custom-strategy.js +0 -313
- package/src/services/ai-supervision/consensus.js +0 -284
- package/src/services/ai-supervision/context.js +0 -275
- package/src/services/ai-supervision/directive.js +0 -167
- package/src/services/ai-supervision/index.js +0 -359
- package/src/services/ai-supervision/parser.js +0 -278
- package/src/services/ai-supervision/symbols.js +0 -259
- package/src/services/cliproxy/index.js +0 -256
- package/src/services/cliproxy/installer.js +0 -111
- package/src/services/cliproxy/manager.js +0 -387
- package/src/services/llmproxy/index.js +0 -166
- package/src/services/llmproxy/manager.js +0 -411
package/src/pages/algo/index.js
CHANGED
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
|
-
const {
|
|
6
|
+
const { getSeparator } = require('../../ui');
|
|
7
7
|
const { logger, prompts } = require('../../utils');
|
|
8
|
-
const { getActiveProvider } = require('../ai-agents');
|
|
9
8
|
|
|
10
9
|
const log = logger.scope('AlgoMenu');
|
|
11
10
|
|
|
12
11
|
const { oneAccountMenu } = require('./one-account');
|
|
13
12
|
const { copyTradingMenu } = require('./copy-trading');
|
|
14
|
-
const { customStrategyMenu } = require('./custom-strategy');
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
15
|
* Algo Trading Menu
|
|
@@ -20,94 +18,39 @@ const algoTradingMenu = async (service) => {
|
|
|
20
18
|
log.info('Algo Trading menu opened');
|
|
21
19
|
|
|
22
20
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const W = boxWidth - 2;
|
|
29
|
-
|
|
30
|
-
// Check if AI agent is connected
|
|
31
|
-
const aiProvider = getActiveProvider();
|
|
32
|
-
const hasAI = !!aiProvider;
|
|
33
|
-
|
|
34
|
-
// Draw menu rectangle
|
|
35
|
-
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
36
|
-
console.log(chalk.cyan('║') + chalk.magenta.bold(centerText('ALGO-TRADING', W)) + chalk.cyan('║'));
|
|
37
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
38
|
-
|
|
39
|
-
// 2 or 3 columns layout based on AI availability
|
|
40
|
-
const col1 = '[1] ONE ACCOUNT';
|
|
41
|
-
const col2 = '[2] COPY TRADING';
|
|
42
|
-
const col3 = hasAI ? '[3] CUSTOM STRATEGY' : '';
|
|
43
|
-
|
|
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
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(chalk.cyan('╠' + '─'.repeat(W) + '╣'));
|
|
71
|
-
console.log(chalk.cyan('║') + chalk.red(centerText('[B] BACK', W)) + chalk.cyan('║'));
|
|
72
|
-
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(chalk.gray(getSeparator()));
|
|
23
|
+
console.log(chalk.yellow.bold(' Algo-Trading'));
|
|
24
|
+
console.log(chalk.gray(getSeparator()));
|
|
25
|
+
console.log();
|
|
73
26
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
27
|
+
const action = await prompts.selectOption(chalk.yellow('Select Mode:'), [
|
|
28
|
+
{ value: 'one_account', label: 'One Account' },
|
|
29
|
+
{ value: 'copy_trading', label: 'Copy Trading' },
|
|
30
|
+
{ value: 'back', label: '< Back' }
|
|
31
|
+
]);
|
|
77
32
|
|
|
78
|
-
log.debug('Algo mode selected', {
|
|
33
|
+
log.debug('Algo mode selected', { action });
|
|
79
34
|
|
|
80
|
-
if (
|
|
35
|
+
if (!action || action === 'back') {
|
|
81
36
|
return 'back';
|
|
82
37
|
}
|
|
83
38
|
|
|
84
|
-
switch (
|
|
85
|
-
case '
|
|
39
|
+
switch (action) {
|
|
40
|
+
case 'one_account':
|
|
86
41
|
log.info('Starting One Account mode');
|
|
87
42
|
await oneAccountMenu(service);
|
|
88
43
|
break;
|
|
89
|
-
case '
|
|
44
|
+
case 'copy_trading':
|
|
90
45
|
log.info('Starting Copy Trading mode');
|
|
91
46
|
await copyTradingMenu();
|
|
92
47
|
break;
|
|
93
|
-
case '3':
|
|
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
|
-
}
|
|
101
|
-
break;
|
|
102
|
-
default:
|
|
103
|
-
console.log(chalk.red(' INVALID OPTION'));
|
|
104
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
105
48
|
}
|
|
106
49
|
|
|
107
|
-
return
|
|
50
|
+
return action;
|
|
108
51
|
} catch (err) {
|
|
109
52
|
log.error('Algo menu error:', err.message);
|
|
110
|
-
console.log(chalk.red(`
|
|
53
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
111
54
|
await prompts.waitForEnter();
|
|
112
55
|
return 'back';
|
|
113
56
|
}
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One Account Mode - HQX
|
|
3
|
-
* Supports multi-agent AI supervision
|
|
2
|
+
* One Account Mode - HQX Algo Trading with Strategy Selection
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
const chalk = require('chalk');
|
|
7
6
|
const ora = require('ora');
|
|
7
|
+
const readline = require('readline');
|
|
8
8
|
|
|
9
9
|
const { connections } = require('../../services');
|
|
10
|
+
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
10
11
|
const { prompts } = require('../../utils');
|
|
11
12
|
const { checkMarketHours } = require('../../services/rithmic/market');
|
|
12
|
-
const { executeAlgo } = require('./algo-executor');
|
|
13
|
-
const { getActiveAgentCount, getSupervisionConfig, getActiveAgents } = require('../ai-agents');
|
|
14
|
-
const { runPreflightCheck, formatPreflightResults, getPreflightSummary } = require('../../services/ai-supervision');
|
|
15
13
|
|
|
14
|
+
// Strategy Registry & Market Data
|
|
15
|
+
const { getAvailableStrategies, loadStrategy, getStrategy } = require('../../lib/m');
|
|
16
|
+
const { MarketDataFeed } = require('../../lib/data');
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Strategy Selection
|
|
21
|
+
* @returns {Promise<string|null>} Selected strategy ID or null
|
|
22
|
+
*/
|
|
23
|
+
const selectStrategy = async () => {
|
|
24
|
+
const strategies = getAvailableStrategies();
|
|
25
|
+
|
|
26
|
+
const options = strategies.map(s => ({
|
|
27
|
+
label: s.id === 'ultra-scalping' ? 'HQX Scalping' : 'HQX Sweep',
|
|
28
|
+
value: s.id
|
|
29
|
+
}));
|
|
30
|
+
options.push({ label: chalk.gray('< Back'), value: 'back' });
|
|
31
|
+
|
|
32
|
+
const selected = await prompts.selectOption('Select Strategy:', options);
|
|
33
|
+
return selected === 'back' ? null : selected;
|
|
34
|
+
};
|
|
16
35
|
|
|
17
36
|
|
|
18
37
|
/**
|
|
@@ -74,60 +93,15 @@ const oneAccountMenu = async (service) => {
|
|
|
74
93
|
const contract = await selectSymbol(accountService, selectedAccount);
|
|
75
94
|
if (!contract) return;
|
|
76
95
|
|
|
96
|
+
// Select strategy
|
|
97
|
+
const strategyId = await selectStrategy();
|
|
98
|
+
if (!strategyId) return;
|
|
99
|
+
|
|
77
100
|
// Configure algo
|
|
78
|
-
const config = await configureAlgo(selectedAccount, contract);
|
|
101
|
+
const config = await configureAlgo(selectedAccount, contract, strategyId);
|
|
79
102
|
if (!config) return;
|
|
80
103
|
|
|
81
|
-
|
|
82
|
-
const agentCount = getActiveAgentCount();
|
|
83
|
-
let supervisionConfig = null;
|
|
84
|
-
|
|
85
|
-
if (agentCount > 0) {
|
|
86
|
-
console.log();
|
|
87
|
-
console.log(chalk.cyan(` ${agentCount} AI Agent(s) available for supervision`));
|
|
88
|
-
const enableAI = await prompts.confirmPrompt('Enable AI Supervision?', true);
|
|
89
|
-
|
|
90
|
-
if (enableAI) {
|
|
91
|
-
// Run pre-flight check - ALL agents must pass
|
|
92
|
-
console.log();
|
|
93
|
-
console.log(chalk.yellow(' Running AI pre-flight check...'));
|
|
94
|
-
console.log();
|
|
95
|
-
|
|
96
|
-
const agents = getActiveAgents();
|
|
97
|
-
const preflightResults = await runPreflightCheck(agents);
|
|
98
|
-
|
|
99
|
-
// Display results
|
|
100
|
-
const lines = formatPreflightResults(preflightResults, 60);
|
|
101
|
-
for (const line of lines) {
|
|
102
|
-
console.log(line);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const summary = getPreflightSummary(preflightResults);
|
|
106
|
-
console.log();
|
|
107
|
-
console.log(` ${summary.text}`);
|
|
108
|
-
console.log();
|
|
109
|
-
|
|
110
|
-
if (!preflightResults.success) {
|
|
111
|
-
console.log(chalk.red(' Cannot start algo - fix agent connections first.'));
|
|
112
|
-
await prompts.waitForEnter();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
supervisionConfig = getSupervisionConfig();
|
|
117
|
-
console.log(chalk.green(` ✓ AI Supervision ready with ${agentCount} agent(s)`));
|
|
118
|
-
|
|
119
|
-
const proceedWithAI = await prompts.confirmPrompt('Start algo with AI supervision?', true);
|
|
120
|
-
if (!proceedWithAI) return;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
await executeAlgo({
|
|
125
|
-
service: accountService,
|
|
126
|
-
account: selectedAccount,
|
|
127
|
-
contract,
|
|
128
|
-
config,
|
|
129
|
-
options: { supervisionConfig }
|
|
130
|
-
});
|
|
104
|
+
await launchAlgo(accountService, selectedAccount, contract, config, strategyId);
|
|
131
105
|
};
|
|
132
106
|
|
|
133
107
|
/**
|
|
@@ -182,9 +156,12 @@ const selectSymbol = async (service, account) => {
|
|
|
182
156
|
/**
|
|
183
157
|
* Configure algo
|
|
184
158
|
*/
|
|
185
|
-
const configureAlgo = async (account, contract) => {
|
|
159
|
+
const configureAlgo = async (account, contract, strategyId) => {
|
|
160
|
+
const strategyInfo = getStrategy(strategyId);
|
|
161
|
+
|
|
186
162
|
console.log();
|
|
187
163
|
console.log(chalk.cyan(' Configure Algo Parameters'));
|
|
164
|
+
console.log(chalk.gray(` Strategy: ${strategyInfo.name}`));
|
|
188
165
|
console.log();
|
|
189
166
|
|
|
190
167
|
const contracts = await prompts.numberInput('Number of contracts:', 1, 1, 10);
|
|
@@ -205,4 +182,292 @@ const configureAlgo = async (account, contract) => {
|
|
|
205
182
|
return { contracts, dailyTarget, maxRisk, showName };
|
|
206
183
|
};
|
|
207
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Launch algo trading - Dynamic Strategy Loading
|
|
187
|
+
* Real-time market data + Strategy signals + Auto order execution
|
|
188
|
+
*/
|
|
189
|
+
const launchAlgo = async (service, account, contract, config, strategyId) => {
|
|
190
|
+
const { contracts, dailyTarget, maxRisk, showName } = config;
|
|
191
|
+
|
|
192
|
+
// Load strategy dynamically
|
|
193
|
+
const strategyInfo = getStrategy(strategyId);
|
|
194
|
+
const strategyModule = loadStrategy(strategyId);
|
|
195
|
+
|
|
196
|
+
// Use RAW API fields
|
|
197
|
+
const accountName = showName
|
|
198
|
+
? (account.accountName || account.rithmicAccountId || account.accountId)
|
|
199
|
+
: 'HQX *****';
|
|
200
|
+
const symbolName = contract.name;
|
|
201
|
+
const contractId = contract.id;
|
|
202
|
+
const connectionType = account.platform || 'Rithmic';
|
|
203
|
+
const tickSize = contract.tickSize || 0.25;
|
|
204
|
+
|
|
205
|
+
const ui = new AlgoUI({ subtitle: strategyInfo.name, mode: 'one-account' });
|
|
206
|
+
|
|
207
|
+
const stats = {
|
|
208
|
+
accountName,
|
|
209
|
+
symbol: symbolName,
|
|
210
|
+
qty: contracts,
|
|
211
|
+
target: dailyTarget,
|
|
212
|
+
risk: maxRisk,
|
|
213
|
+
propfirm: account.propfirm || 'Unknown',
|
|
214
|
+
platform: connectionType,
|
|
215
|
+
pnl: 0,
|
|
216
|
+
trades: 0,
|
|
217
|
+
wins: 0,
|
|
218
|
+
losses: 0,
|
|
219
|
+
latency: 0,
|
|
220
|
+
connected: false,
|
|
221
|
+
startTime: Date.now()
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
let running = true;
|
|
225
|
+
let stopReason = null;
|
|
226
|
+
let startingPnL = null;
|
|
227
|
+
let currentPosition = 0; // Current position qty (+ long, - short)
|
|
228
|
+
let pendingOrder = false; // Prevent duplicate orders
|
|
229
|
+
let tickCount = 0;
|
|
230
|
+
|
|
231
|
+
// Initialize Strategy dynamically
|
|
232
|
+
const strategy = new strategyModule.M1({ tickSize });
|
|
233
|
+
strategy.initialize(contractId, tickSize);
|
|
234
|
+
|
|
235
|
+
// Initialize Market Data Feed
|
|
236
|
+
const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
|
|
237
|
+
|
|
238
|
+
// Log startup
|
|
239
|
+
ui.addLog('info', `Strategy: ${strategyInfo.name}`);
|
|
240
|
+
ui.addLog('info', `Connection: ${connectionType}`);
|
|
241
|
+
ui.addLog('info', `Account: ${accountName}`);
|
|
242
|
+
ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
|
|
243
|
+
ui.addLog('info', `Target: $${dailyTarget} | Max Risk: $${maxRisk}`);
|
|
244
|
+
ui.addLog('info', `Params: ${strategyInfo.params.stopTicks}t stop, ${strategyInfo.params.targetTicks}t target (${strategyInfo.params.riskReward})`);
|
|
245
|
+
ui.addLog('info', 'Connecting to market data...');
|
|
246
|
+
|
|
247
|
+
// Handle strategy signals
|
|
248
|
+
strategy.on('signal', async (signal) => {
|
|
249
|
+
if (!running || pendingOrder || currentPosition !== 0) return;
|
|
250
|
+
|
|
251
|
+
const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
|
|
252
|
+
|
|
253
|
+
ui.addLog('signal', `${direction.toUpperCase()} signal @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
|
|
254
|
+
|
|
255
|
+
// Place order via API
|
|
256
|
+
pendingOrder = true;
|
|
257
|
+
try {
|
|
258
|
+
const orderSide = direction === 'long' ? 0 : 1; // 0=Buy, 1=Sell
|
|
259
|
+
const orderResult = await service.placeOrder({
|
|
260
|
+
accountId: account.accountId,
|
|
261
|
+
contractId: contractId,
|
|
262
|
+
type: 2, // Market order
|
|
263
|
+
side: orderSide,
|
|
264
|
+
size: contracts
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (orderResult.success) {
|
|
268
|
+
currentPosition = direction === 'long' ? contracts : -contracts;
|
|
269
|
+
stats.trades++;
|
|
270
|
+
ui.addLog('trade', `OPENED ${direction.toUpperCase()} ${contracts}x @ market`);
|
|
271
|
+
|
|
272
|
+
// Place bracket orders (SL/TP)
|
|
273
|
+
if (stopLoss && takeProfit) {
|
|
274
|
+
// Stop Loss
|
|
275
|
+
await service.placeOrder({
|
|
276
|
+
accountId: account.accountId,
|
|
277
|
+
contractId: contractId,
|
|
278
|
+
type: 4, // Stop order
|
|
279
|
+
side: direction === 'long' ? 1 : 0, // Opposite side
|
|
280
|
+
size: contracts,
|
|
281
|
+
stopPrice: stopLoss
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Take Profit
|
|
285
|
+
await service.placeOrder({
|
|
286
|
+
accountId: account.accountId,
|
|
287
|
+
contractId: contractId,
|
|
288
|
+
type: 1, // Limit order
|
|
289
|
+
side: direction === 'long' ? 1 : 0,
|
|
290
|
+
size: contracts,
|
|
291
|
+
limitPrice: takeProfit
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
ui.addLog('error', `Order failed: ${orderResult.error}`);
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
ui.addLog('error', `Order error: ${e.message}`);
|
|
301
|
+
}
|
|
302
|
+
pendingOrder = false;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Handle market data ticks
|
|
306
|
+
marketFeed.on('tick', (tick) => {
|
|
307
|
+
tickCount++;
|
|
308
|
+
const latencyStart = Date.now();
|
|
309
|
+
|
|
310
|
+
// Feed tick to strategy
|
|
311
|
+
strategy.processTick({
|
|
312
|
+
contractId: tick.contractId || contractId,
|
|
313
|
+
price: tick.price,
|
|
314
|
+
bid: tick.bid,
|
|
315
|
+
ask: tick.ask,
|
|
316
|
+
volume: tick.volume || 1,
|
|
317
|
+
side: tick.lastTradeSide || 'unknown',
|
|
318
|
+
timestamp: tick.timestamp || Date.now()
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
stats.latency = Date.now() - latencyStart;
|
|
322
|
+
|
|
323
|
+
// Log every 100th tick to show activity
|
|
324
|
+
if (tickCount % 100 === 0) {
|
|
325
|
+
ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
marketFeed.on('connected', () => {
|
|
330
|
+
stats.connected = true;
|
|
331
|
+
ui.addLog('success', 'Market data connected!');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
marketFeed.on('error', (err) => {
|
|
335
|
+
ui.addLog('error', `Market: ${err.message}`);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
marketFeed.on('disconnected', () => {
|
|
339
|
+
stats.connected = false;
|
|
340
|
+
ui.addLog('error', 'Market data disconnected');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Connect to market data
|
|
344
|
+
try {
|
|
345
|
+
const token = service.token || service.getToken?.();
|
|
346
|
+
const propfirmKey = (account.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
|
|
347
|
+
await marketFeed.connect(token, propfirmKey, contractId);
|
|
348
|
+
await marketFeed.subscribe(symbolName, contractId);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
ui.addLog('error', `Failed to connect: ${e.message}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Poll account P&L from API
|
|
354
|
+
const pollPnL = async () => {
|
|
355
|
+
try {
|
|
356
|
+
const accountResult = await service.getTradingAccounts();
|
|
357
|
+
if (accountResult.success && accountResult.accounts) {
|
|
358
|
+
const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
|
|
359
|
+
if (acc && acc.profitAndLoss !== undefined) {
|
|
360
|
+
if (startingPnL === null) startingPnL = acc.profitAndLoss;
|
|
361
|
+
stats.pnl = acc.profitAndLoss - startingPnL;
|
|
362
|
+
|
|
363
|
+
// Record trade result in strategy
|
|
364
|
+
if (stats.pnl !== 0) {
|
|
365
|
+
strategy.recordTradeResult(stats.pnl);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Check positions
|
|
371
|
+
const posResult = await service.getPositions(account.accountId);
|
|
372
|
+
if (posResult.success && posResult.positions) {
|
|
373
|
+
const pos = posResult.positions.find(p => {
|
|
374
|
+
const sym = p.contractId || p.symbol || '';
|
|
375
|
+
return sym.includes(contract.name) || sym.includes(contractId);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (pos && pos.quantity !== 0) {
|
|
379
|
+
currentPosition = pos.quantity;
|
|
380
|
+
const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
|
|
381
|
+
const pnl = pos.profitAndLoss || 0;
|
|
382
|
+
|
|
383
|
+
// Check if position closed (win/loss)
|
|
384
|
+
if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
|
|
385
|
+
else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
|
|
386
|
+
} else {
|
|
387
|
+
currentPosition = 0;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check target/risk
|
|
392
|
+
if (stats.pnl >= dailyTarget) {
|
|
393
|
+
stopReason = 'target';
|
|
394
|
+
running = false;
|
|
395
|
+
ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
|
|
396
|
+
} else if (stats.pnl <= -maxRisk) {
|
|
397
|
+
stopReason = 'risk';
|
|
398
|
+
running = false;
|
|
399
|
+
ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
400
|
+
}
|
|
401
|
+
} catch (e) {
|
|
402
|
+
// Silently handle polling errors
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// Start polling and UI refresh
|
|
407
|
+
const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
|
|
408
|
+
const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
|
|
409
|
+
pollPnL(); // Initial poll
|
|
410
|
+
|
|
411
|
+
// Keyboard handler
|
|
412
|
+
const setupKeyHandler = () => {
|
|
413
|
+
if (!process.stdin.isTTY) return;
|
|
414
|
+
readline.emitKeypressEvents(process.stdin);
|
|
415
|
+
process.stdin.setRawMode(true);
|
|
416
|
+
process.stdin.resume();
|
|
417
|
+
|
|
418
|
+
const onKey = (str, key) => {
|
|
419
|
+
if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
|
|
420
|
+
running = false;
|
|
421
|
+
stopReason = 'manual';
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
process.stdin.on('keypress', onKey);
|
|
425
|
+
return () => {
|
|
426
|
+
process.stdin.removeListener('keypress', onKey);
|
|
427
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
428
|
+
};
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const cleanupKeys = setupKeyHandler();
|
|
432
|
+
|
|
433
|
+
// Wait for stop
|
|
434
|
+
await new Promise(resolve => {
|
|
435
|
+
const check = setInterval(() => {
|
|
436
|
+
if (!running) {
|
|
437
|
+
clearInterval(check);
|
|
438
|
+
resolve();
|
|
439
|
+
}
|
|
440
|
+
}, 100);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// Cleanup
|
|
444
|
+
clearInterval(refreshInterval);
|
|
445
|
+
clearInterval(pnlInterval);
|
|
446
|
+
await marketFeed.disconnect();
|
|
447
|
+
if (cleanupKeys) cleanupKeys();
|
|
448
|
+
ui.cleanup();
|
|
449
|
+
|
|
450
|
+
if (process.stdin.isTTY) {
|
|
451
|
+
process.stdin.setRawMode(false);
|
|
452
|
+
}
|
|
453
|
+
process.stdin.resume();
|
|
454
|
+
|
|
455
|
+
// Duration
|
|
456
|
+
const durationMs = Date.now() - stats.startTime;
|
|
457
|
+
const hours = Math.floor(durationMs / 3600000);
|
|
458
|
+
const minutes = Math.floor((durationMs % 3600000) / 60000);
|
|
459
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
460
|
+
stats.duration = hours > 0
|
|
461
|
+
? `${hours}h ${minutes}m ${seconds}s`
|
|
462
|
+
: minutes > 0
|
|
463
|
+
? `${minutes}m ${seconds}s`
|
|
464
|
+
: `${seconds}s`;
|
|
465
|
+
|
|
466
|
+
// Summary
|
|
467
|
+
renderSessionSummary(stats, stopReason);
|
|
468
|
+
|
|
469
|
+
console.log('\n Returning to menu in 3 seconds...');
|
|
470
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
471
|
+
};
|
|
472
|
+
|
|
208
473
|
module.exports = { oneAccountMenu };
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -127,9 +127,9 @@ 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.
|
|
130
|
+
this._line(chalk.cyan(BOX.V) + chalk.white(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
|
-
this._line(chalk.cyan(BOX.V) + chalk.yellow(center(
|
|
132
|
+
this._line(chalk.cyan(BOX.V) + chalk.yellow(center(this.config.subtitle || 'HQX Algo Trading', W)) + chalk.cyan(BOX.V));
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
_drawStats(stats) {
|
|
@@ -293,7 +293,7 @@ class AlgoUI {
|
|
|
293
293
|
const visible = logs.slice(-maxLogs).reverse();
|
|
294
294
|
|
|
295
295
|
if (visible.length === 0) {
|
|
296
|
-
this._line(chalk.cyan(BOX.V) + chalk.gray(fitToWidth('
|
|
296
|
+
this._line(chalk.cyan(BOX.V) + chalk.gray(fitToWidth(' Awaiting market signals...', W)) + chalk.cyan(BOX.V));
|
|
297
297
|
for (let i = 0; i < maxLogs - 1; i++) {
|
|
298
298
|
this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
|
|
299
299
|
}
|
|
@@ -355,11 +355,11 @@ const checkMarketStatus = () => {
|
|
|
355
355
|
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
356
356
|
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
357
357
|
|
|
358
|
-
if (ctDay === 6) return { isOpen: false, message: '
|
|
359
|
-
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: '
|
|
360
|
-
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: '
|
|
361
|
-
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: '
|
|
362
|
-
return { isOpen: true, message: '
|
|
358
|
+
if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
359
|
+
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
360
|
+
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
361
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance' };
|
|
362
|
+
return { isOpen: true, message: 'Market OPEN' };
|
|
363
363
|
};
|
|
364
364
|
|
|
365
365
|
/**
|
|
@@ -371,7 +371,7 @@ const renderSessionSummary = (stats, stopReason) => {
|
|
|
371
371
|
const colR = W - colL - 1;
|
|
372
372
|
const version = require('../../../package.json').version;
|
|
373
373
|
|
|
374
|
-
|
|
374
|
+
console.clear();
|
|
375
375
|
console.log();
|
|
376
376
|
|
|
377
377
|
// Top border
|
|
@@ -387,9 +387,9 @@ 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.
|
|
390
|
+
console.log(chalk.cyan(BOX.V) + chalk.white(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
|
-
console.log(chalk.cyan(BOX.V) + chalk.yellow(center('
|
|
392
|
+
console.log(chalk.cyan(BOX.V) + chalk.yellow(center('Session Summary', W)) + chalk.cyan(BOX.V));
|
|
393
393
|
|
|
394
394
|
// Grid separators
|
|
395
395
|
const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
|
|
@@ -410,18 +410,18 @@ const renderSessionSummary = (stats, stopReason) => {
|
|
|
410
410
|
// Row 1: Stop Reason | Duration
|
|
411
411
|
const duration = stats.duration || '--';
|
|
412
412
|
const reasonColor = stopReason === 'target' ? chalk.green : stopReason === 'risk' ? chalk.red : chalk.yellow;
|
|
413
|
-
row('
|
|
413
|
+
row('Stop Reason', (stopReason || 'manual').toUpperCase(), reasonColor, 'Duration', duration, chalk.white);
|
|
414
414
|
|
|
415
415
|
console.log(chalk.cyan(GM));
|
|
416
416
|
|
|
417
417
|
// Row 2: Trades | Win Rate
|
|
418
418
|
const winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) + '%' : '0%';
|
|
419
|
-
row('
|
|
419
|
+
row('Trades', String(stats.trades || 0), chalk.white, 'Win Rate', winRate, stats.wins >= stats.losses ? chalk.green : chalk.red);
|
|
420
420
|
|
|
421
421
|
console.log(chalk.cyan(GM));
|
|
422
422
|
|
|
423
423
|
// Row 3: Wins | Losses
|
|
424
|
-
row('
|
|
424
|
+
row('Wins', String(stats.wins || 0), chalk.green, 'Losses', String(stats.losses || 0), chalk.red);
|
|
425
425
|
|
|
426
426
|
console.log(chalk.cyan(GM));
|
|
427
427
|
|
|
@@ -430,7 +430,7 @@ const renderSessionSummary = (stats, stopReason) => {
|
|
|
430
430
|
const pnlStr = `${pnl >= 0 ? '+' : ''}$${Math.abs(pnl).toFixed(2)}`;
|
|
431
431
|
const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
|
|
432
432
|
const targetStr = `$${(stats.target || 0).toFixed(2)}`;
|
|
433
|
-
row('P&L', pnlStr, pnlColor, '
|
|
433
|
+
row('P&L', pnlStr, pnlColor, 'Target', targetStr, chalk.cyan);
|
|
434
434
|
|
|
435
435
|
// Bottom border
|
|
436
436
|
console.log(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
|