hedgequantx 2.9.20 → 2.9.22
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 +64 -42
- package/src/menus/connect.js +17 -14
- package/src/menus/dashboard.js +76 -58
- package/src/pages/accounts.js +49 -38
- package/src/pages/ai-agents-ui.js +388 -0
- package/src/pages/ai-agents.js +494 -0
- package/src/pages/ai-models.js +389 -0
- package/src/pages/algo/algo-executor.js +307 -0
- package/src/pages/algo/copy-executor.js +331 -0
- package/src/pages/algo/copy-trading.js +178 -546
- package/src/pages/algo/custom-strategy.js +313 -0
- package/src/pages/algo/index.js +75 -18
- package/src/pages/algo/one-account.js +57 -322
- package/src/pages/algo/ui.js +15 -15
- package/src/pages/orders.js +22 -19
- package/src/pages/positions.js +22 -19
- package/src/pages/stats/index.js +16 -15
- package/src/pages/user.js +11 -7
- package/src/services/ai-supervision/consensus.js +284 -0
- package/src/services/ai-supervision/context.js +275 -0
- package/src/services/ai-supervision/directive.js +167 -0
- package/src/services/ai-supervision/health.js +47 -35
- package/src/services/ai-supervision/index.js +359 -0
- package/src/services/ai-supervision/parser.js +278 -0
- package/src/services/ai-supervision/symbols.js +259 -0
- package/src/services/cliproxy/index.js +256 -0
- package/src/services/cliproxy/installer.js +111 -0
- package/src/services/cliproxy/manager.js +387 -0
- package/src/services/index.js +9 -1
- package/src/services/llmproxy/index.js +166 -0
- package/src/services/llmproxy/manager.js +411 -0
- package/src/services/rithmic/accounts.js +6 -8
- package/src/ui/box.js +5 -9
- package/src/ui/index.js +18 -5
- package/src/ui/menu.js +4 -4
|
@@ -1,37 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One Account Mode - HQX
|
|
2
|
+
* One Account Mode - HQX Ultra Scalping
|
|
3
|
+
* Supports multi-agent AI supervision
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
const chalk = require('chalk');
|
|
6
7
|
const ora = require('ora');
|
|
7
|
-
const readline = require('readline');
|
|
8
8
|
|
|
9
9
|
const { connections } = require('../../services');
|
|
10
|
-
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
11
10
|
const { prompts } = require('../../utils');
|
|
12
11
|
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');
|
|
13
15
|
|
|
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
|
-
};
|
|
35
16
|
|
|
36
17
|
|
|
37
18
|
/**
|
|
@@ -93,15 +74,60 @@ const oneAccountMenu = async (service) => {
|
|
|
93
74
|
const contract = await selectSymbol(accountService, selectedAccount);
|
|
94
75
|
if (!contract) return;
|
|
95
76
|
|
|
96
|
-
// Select strategy
|
|
97
|
-
const strategyId = await selectStrategy();
|
|
98
|
-
if (!strategyId) return;
|
|
99
|
-
|
|
100
77
|
// Configure algo
|
|
101
|
-
const config = await configureAlgo(selectedAccount, contract
|
|
78
|
+
const config = await configureAlgo(selectedAccount, contract);
|
|
102
79
|
if (!config) return;
|
|
103
80
|
|
|
104
|
-
|
|
81
|
+
// Check for AI Supervision
|
|
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
|
+
});
|
|
105
131
|
};
|
|
106
132
|
|
|
107
133
|
/**
|
|
@@ -156,12 +182,9 @@ const selectSymbol = async (service, account) => {
|
|
|
156
182
|
/**
|
|
157
183
|
* Configure algo
|
|
158
184
|
*/
|
|
159
|
-
const configureAlgo = async (account, contract
|
|
160
|
-
const strategyInfo = getStrategy(strategyId);
|
|
161
|
-
|
|
185
|
+
const configureAlgo = async (account, contract) => {
|
|
162
186
|
console.log();
|
|
163
187
|
console.log(chalk.cyan(' Configure Algo Parameters'));
|
|
164
|
-
console.log(chalk.gray(` Strategy: ${strategyInfo.name}`));
|
|
165
188
|
console.log();
|
|
166
189
|
|
|
167
190
|
const contracts = await prompts.numberInput('Number of contracts:', 1, 1, 10);
|
|
@@ -182,292 +205,4 @@ const configureAlgo = async (account, contract, strategyId) => {
|
|
|
182
205
|
return { contracts, dailyTarget, maxRisk, showName };
|
|
183
206
|
};
|
|
184
207
|
|
|
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
|
-
|
|
473
208
|
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.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
|
-
this._line(chalk.cyan(BOX.V) + chalk.yellow(center(this.config.subtitle || 'HQX
|
|
132
|
+
this._line(chalk.cyan(BOX.V) + chalk.yellow(center((this.config.subtitle || 'HQX ALGO TRADING').toUpperCase(), 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
|
+
process.stdout.write('\x1B[2J\x1B[H');
|
|
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.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
|
-
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));
|
package/src/pages/orders.js
CHANGED
|
@@ -7,37 +7,40 @@ const ora = require('ora');
|
|
|
7
7
|
|
|
8
8
|
const { connections } = require('../services');
|
|
9
9
|
const { ORDER_STATUS, ORDER_TYPE, ORDER_SIDE } = require('../config');
|
|
10
|
-
const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator } = require('../ui');
|
|
10
|
+
const { getLogoWidth, drawBoxHeader, drawBoxFooter, drawBoxRow, drawBoxSeparator, displayBanner, clearScreen } = require('../ui');
|
|
11
11
|
const { prompts } = require('../utils');
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Show all orders
|
|
15
15
|
*/
|
|
16
16
|
const showOrders = async (service) => {
|
|
17
|
+
// Clear screen and show banner
|
|
18
|
+
clearScreen();
|
|
19
|
+
displayBanner();
|
|
20
|
+
|
|
17
21
|
const boxWidth = getLogoWidth();
|
|
18
22
|
let spinner;
|
|
19
23
|
|
|
20
24
|
try {
|
|
21
|
-
|
|
22
|
-
spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
|
|
25
|
+
spinner = ora({ text: 'LOADING ORDERS...', color: 'yellow' }).start();
|
|
23
26
|
|
|
24
27
|
const allConns = connections.count() > 0
|
|
25
28
|
? connections.getAll()
|
|
26
29
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
27
30
|
|
|
28
31
|
if (allConns.length === 0) {
|
|
29
|
-
spinner.fail('
|
|
32
|
+
spinner.fail('NO CONNECTIONS FOUND');
|
|
30
33
|
await prompts.waitForEnter();
|
|
31
34
|
return;
|
|
32
35
|
}
|
|
33
|
-
spinner.succeed(`
|
|
36
|
+
spinner.succeed(`FOUND ${allConns.length} CONNECTION(S)`);
|
|
34
37
|
|
|
35
38
|
// Step 2: Fetch accounts
|
|
36
39
|
let allAccounts = [];
|
|
37
40
|
|
|
38
41
|
for (const conn of allConns) {
|
|
39
42
|
const propfirmName = conn.propfirm || conn.type || 'Unknown';
|
|
40
|
-
spinner = ora({ text: `
|
|
43
|
+
spinner = ora({ text: `FETCHING ACCOUNTS FROM ${propfirmName.toUpperCase()}...`, color: 'yellow' }).start();
|
|
41
44
|
|
|
42
45
|
try {
|
|
43
46
|
const result = await conn.service.getTradingAccounts();
|
|
@@ -49,17 +52,17 @@ const showOrders = async (service) => {
|
|
|
49
52
|
service: conn.service
|
|
50
53
|
});
|
|
51
54
|
});
|
|
52
|
-
spinner.succeed(`${propfirmName}: ${result.accounts.length}
|
|
55
|
+
spinner.succeed(`${propfirmName.toUpperCase()}: ${result.accounts.length} ACCOUNT(S)`);
|
|
53
56
|
} else {
|
|
54
|
-
spinner.warn(`${propfirmName}:
|
|
57
|
+
spinner.warn(`${propfirmName.toUpperCase()}: NO ACCOUNTS`);
|
|
55
58
|
}
|
|
56
59
|
} catch (e) {
|
|
57
|
-
spinner.fail(`${propfirmName}:
|
|
60
|
+
spinner.fail(`${propfirmName.toUpperCase()}: FAILED`);
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
if (allAccounts.length === 0) {
|
|
62
|
-
console.log(chalk.yellow('\n
|
|
65
|
+
console.log(chalk.yellow('\n NO ACCOUNTS FOUND.'));
|
|
63
66
|
await prompts.waitForEnter();
|
|
64
67
|
return;
|
|
65
68
|
}
|
|
@@ -69,7 +72,7 @@ const showOrders = async (service) => {
|
|
|
69
72
|
|
|
70
73
|
for (const account of allAccounts) {
|
|
71
74
|
const accName = String(account.accountName || account.rithmicAccountId || account.accountId || 'Unknown').substring(0, 20);
|
|
72
|
-
spinner = ora({ text: `
|
|
75
|
+
spinner = ora({ text: `FETCHING ORDERS FOR ${accName.toUpperCase()}...`, color: 'yellow' }).start();
|
|
73
76
|
|
|
74
77
|
try {
|
|
75
78
|
const result = await account.service.getOrders(account.accountId);
|
|
@@ -81,26 +84,26 @@ const showOrders = async (service) => {
|
|
|
81
84
|
propfirm: account.propfirm
|
|
82
85
|
});
|
|
83
86
|
});
|
|
84
|
-
spinner.succeed(`${accName}: ${result.orders.length}
|
|
87
|
+
spinner.succeed(`${accName.toUpperCase()}: ${result.orders.length} ORDER(S)`);
|
|
85
88
|
} else {
|
|
86
|
-
spinner.succeed(`${accName}:
|
|
89
|
+
spinner.succeed(`${accName.toUpperCase()}: NO ORDERS`);
|
|
87
90
|
}
|
|
88
91
|
} catch (e) {
|
|
89
|
-
spinner.fail(`${accName}:
|
|
92
|
+
spinner.fail(`${accName.toUpperCase()}: FAILED TO FETCH ORDERS`);
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
spinner = ora({ text: '
|
|
94
|
-
spinner.succeed(`
|
|
96
|
+
spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
|
|
97
|
+
spinner.succeed(`TOTAL: ${allOrders.length} ORDER(S)`);
|
|
95
98
|
console.log();
|
|
96
99
|
|
|
97
100
|
// Display
|
|
98
101
|
drawBoxHeader('ORDERS', boxWidth);
|
|
99
102
|
|
|
100
103
|
if (allOrders.length === 0) {
|
|
101
|
-
drawBoxRow(chalk.gray('
|
|
104
|
+
drawBoxRow(chalk.gray(' NO ORDERS FOUND'), boxWidth);
|
|
102
105
|
} else {
|
|
103
|
-
const header = ' ' + '
|
|
106
|
+
const header = ' ' + 'SYMBOL'.padEnd(12) + 'SIDE'.padEnd(6) + 'TYPE'.padEnd(8) + 'QTY'.padEnd(6) + 'PRICE'.padEnd(10) + 'STATUS'.padEnd(12) + 'ACCOUNT';
|
|
104
107
|
drawBoxRow(chalk.white.bold(header), boxWidth);
|
|
105
108
|
drawBoxSeparator(boxWidth);
|
|
106
109
|
|
|
@@ -130,7 +133,7 @@ const showOrders = async (service) => {
|
|
|
130
133
|
console.log();
|
|
131
134
|
|
|
132
135
|
} catch (error) {
|
|
133
|
-
if (spinner) spinner.fail('
|
|
136
|
+
if (spinner) spinner.fail('ERROR: ' + error.message.toUpperCase());
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
await prompts.waitForEnter();
|