hedgequantx 1.5.7 → 1.5.9
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 +6 -42
- package/src/config/contracts.js +100 -0
- package/src/menus/connect.js +48 -67
- package/src/menus/dashboard.js +101 -146
- package/src/pages/algo/copy-trading.js +15 -8
- package/src/pages/algo/ui.js +37 -19
- package/src/services/projectx/index.js +3 -23
- package/src/services/rithmic/index.js +4 -21
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -189,51 +189,15 @@ const banner = async () => {
|
|
|
189
189
|
const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
|
|
190
190
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
191
191
|
|
|
192
|
-
// Stats bar if connected
|
|
193
|
-
// STRICT: Only display verified values from API, show '--' for unavailable data
|
|
194
|
-
if (statsInfo) {
|
|
195
|
-
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
196
|
-
|
|
197
|
-
const connStr = `Connections: ${statsInfo.connections}`;
|
|
198
|
-
const accStr = `Accounts: ${statsInfo.accounts}`;
|
|
199
|
-
|
|
200
|
-
// Balance: show '--' if not available from API
|
|
201
|
-
const balStr = statsInfo.balance !== null
|
|
202
|
-
? `Balance: $${statsInfo.balance.toLocaleString()}`
|
|
203
|
-
: `Balance: --`;
|
|
204
|
-
const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
|
|
205
|
-
|
|
206
|
-
// P&L: show '--' if not available from API
|
|
207
|
-
let pnlDisplay;
|
|
208
|
-
let pnlColor;
|
|
209
|
-
if (statsInfo.pnl !== null) {
|
|
210
|
-
const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
|
|
211
|
-
pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
212
|
-
pnlDisplay = `$${statsInfo.pnl.toLocaleString()} (${pnlSign}${statsInfo.pnl.toFixed(1)})`;
|
|
213
|
-
} else {
|
|
214
|
-
pnlColor = chalk.gray;
|
|
215
|
-
pnlDisplay = '--';
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Build full stats text and calculate padding
|
|
219
|
-
const statsText = `${connStr} ${accStr} ${balStr} P&L: ${pnlDisplay}`;
|
|
220
|
-
const statsLen = statsText.length;
|
|
221
|
-
const statsLeftPad = Math.floor((innerWidth - statsLen) / 2);
|
|
222
|
-
const statsRightPad = innerWidth - statsLen - statsLeftPad;
|
|
223
|
-
|
|
224
|
-
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
225
|
-
chalk.white(connStr) + ' ' +
|
|
226
|
-
chalk.white(accStr) + ' ' +
|
|
227
|
-
chalk.white('Balance: ') + balColor(statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--') + ' ' +
|
|
228
|
-
chalk.white('P&L: ') + pnlColor(pnlDisplay) +
|
|
229
|
-
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║')
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
192
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
234
193
|
console.log();
|
|
235
194
|
};
|
|
236
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Get cached stats for dashboard
|
|
198
|
+
*/
|
|
199
|
+
const getCachedStats = () => cachedStats;
|
|
200
|
+
|
|
237
201
|
/**
|
|
238
202
|
* Main connection menu
|
|
239
203
|
*/
|
|
@@ -412,4 +376,4 @@ const run = async () => {
|
|
|
412
376
|
}
|
|
413
377
|
};
|
|
414
378
|
|
|
415
|
-
module.exports = { run, banner, mainMenu, dashboardMenu };
|
|
379
|
+
module.exports = { run, banner, mainMenu, dashboardMenu, getCachedStats };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contracts configuration
|
|
3
|
+
* Used by both Rithmic and ProjectX services
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Current front-month contracts (update monthly as contracts expire)
|
|
7
|
+
const CONTRACTS = [
|
|
8
|
+
// Index Futures - Most Popular
|
|
9
|
+
{ symbol: 'ES', name: 'E-mini S&P 500', exchange: 'CME', group: 'Index' },
|
|
10
|
+
{ symbol: 'NQ', name: 'E-mini NASDAQ-100', exchange: 'CME', group: 'Index' },
|
|
11
|
+
{ symbol: 'RTY', name: 'E-mini Russell 2000', exchange: 'CME', group: 'Index' },
|
|
12
|
+
{ symbol: 'YM', name: 'E-mini Dow Jones', exchange: 'CBOT', group: 'Index' },
|
|
13
|
+
|
|
14
|
+
// Micro Index Futures
|
|
15
|
+
{ symbol: 'MES', name: 'Micro E-mini S&P 500', exchange: 'CME', group: 'Micro' },
|
|
16
|
+
{ symbol: 'MNQ', name: 'Micro E-mini NASDAQ-100', exchange: 'CME', group: 'Micro' },
|
|
17
|
+
{ symbol: 'M2K', name: 'Micro E-mini Russell 2000', exchange: 'CME', group: 'Micro' },
|
|
18
|
+
{ symbol: 'MYM', name: 'Micro E-mini Dow Jones', exchange: 'CBOT', group: 'Micro' },
|
|
19
|
+
|
|
20
|
+
// Energy Futures
|
|
21
|
+
{ symbol: 'CL', name: 'Crude Oil', exchange: 'NYMEX', group: 'Energy' },
|
|
22
|
+
{ symbol: 'NG', name: 'Natural Gas', exchange: 'NYMEX', group: 'Energy' },
|
|
23
|
+
{ symbol: 'MCL', name: 'Micro Crude Oil', exchange: 'NYMEX', group: 'Energy' },
|
|
24
|
+
|
|
25
|
+
// Metals Futures
|
|
26
|
+
{ symbol: 'GC', name: 'Gold', exchange: 'COMEX', group: 'Metals' },
|
|
27
|
+
{ symbol: 'SI', name: 'Silver', exchange: 'COMEX', group: 'Metals' },
|
|
28
|
+
{ symbol: 'HG', name: 'Copper', exchange: 'COMEX', group: 'Metals' },
|
|
29
|
+
{ symbol: 'MGC', name: 'Micro Gold', exchange: 'COMEX', group: 'Metals' },
|
|
30
|
+
|
|
31
|
+
// Treasury Futures
|
|
32
|
+
{ symbol: 'ZB', name: '30-Year Treasury Bond', exchange: 'CBOT', group: 'Bonds' },
|
|
33
|
+
{ symbol: 'ZN', name: '10-Year Treasury Note', exchange: 'CBOT', group: 'Bonds' },
|
|
34
|
+
{ symbol: 'ZF', name: '5-Year Treasury Note', exchange: 'CBOT', group: 'Bonds' },
|
|
35
|
+
|
|
36
|
+
// Agriculture Futures
|
|
37
|
+
{ symbol: 'ZC', name: 'Corn', exchange: 'CBOT', group: 'Agriculture' },
|
|
38
|
+
{ symbol: 'ZS', name: 'Soybeans', exchange: 'CBOT', group: 'Agriculture' },
|
|
39
|
+
{ symbol: 'ZW', name: 'Wheat', exchange: 'CBOT', group: 'Agriculture' },
|
|
40
|
+
|
|
41
|
+
// Currency Futures
|
|
42
|
+
{ symbol: '6E', name: 'Euro FX', exchange: 'CME', group: 'Currency' },
|
|
43
|
+
{ symbol: '6B', name: 'British Pound', exchange: 'CME', group: 'Currency' },
|
|
44
|
+
{ symbol: '6J', name: 'Japanese Yen', exchange: 'CME', group: 'Currency' },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get current front-month code based on date
|
|
49
|
+
* Futures months: F(Jan), G(Feb), H(Mar), J(Apr), K(May), M(Jun),
|
|
50
|
+
* N(Jul), Q(Aug), U(Sep), V(Oct), X(Nov), Z(Dec)
|
|
51
|
+
*/
|
|
52
|
+
const getMonthCode = (monthsAhead = 0) => {
|
|
53
|
+
const codes = ['F', 'G', 'H', 'J', 'K', 'M', 'N', 'Q', 'U', 'V', 'X', 'Z'];
|
|
54
|
+
const now = new Date();
|
|
55
|
+
const month = (now.getMonth() + monthsAhead) % 12;
|
|
56
|
+
return codes[month];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get year code (last digit)
|
|
61
|
+
*/
|
|
62
|
+
const getYearCode = (monthsAhead = 0) => {
|
|
63
|
+
const now = new Date();
|
|
64
|
+
const futureDate = new Date(now.getFullYear(), now.getMonth() + monthsAhead, 1);
|
|
65
|
+
return futureDate.getFullYear() % 10;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get contracts with current front-month symbols
|
|
70
|
+
* @param {number} monthsAhead - How many months ahead for the contract (default 2 for front month)
|
|
71
|
+
*/
|
|
72
|
+
const getContractsWithMonthCode = (monthsAhead = 2) => {
|
|
73
|
+
const monthCode = getMonthCode(monthsAhead);
|
|
74
|
+
const yearCode = getYearCode(monthsAhead);
|
|
75
|
+
|
|
76
|
+
return CONTRACTS.map(c => ({
|
|
77
|
+
...c,
|
|
78
|
+
symbol: `${c.symbol}${monthCode}${yearCode}`,
|
|
79
|
+
name: `${c.name}`,
|
|
80
|
+
baseSymbol: c.symbol
|
|
81
|
+
}));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get display name for a symbol
|
|
86
|
+
*/
|
|
87
|
+
const getContractDisplayName = (symbol) => {
|
|
88
|
+
// Extract base symbol (remove month/year code)
|
|
89
|
+
const baseSymbol = symbol.replace(/[A-Z][0-9]$/, '').replace(/[FGHJKMNQUVXZ][0-9]+$/, '');
|
|
90
|
+
const contract = CONTRACTS.find(c => c.symbol === baseSymbol);
|
|
91
|
+
return contract ? contract.name : symbol;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
CONTRACTS,
|
|
96
|
+
getMonthCode,
|
|
97
|
+
getYearCode,
|
|
98
|
+
getContractsWithMonthCode,
|
|
99
|
+
getContractDisplayName
|
|
100
|
+
};
|
package/src/menus/connect.js
CHANGED
|
@@ -65,9 +65,8 @@ const loginPrompt = async (propfirmName) => {
|
|
|
65
65
|
const projectXMenu = async () => {
|
|
66
66
|
const propfirms = getPropFirmsByPlatform('ProjectX');
|
|
67
67
|
const boxWidth = getLogoWidth();
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const colWidth = Math.floor(innerWidth / numCols);
|
|
68
|
+
const W = boxWidth - 2; // Inner width
|
|
69
|
+
const col1Width = Math.floor(W / 2);
|
|
71
70
|
|
|
72
71
|
// Build numbered list
|
|
73
72
|
const numbered = propfirms.map((pf, i) => ({
|
|
@@ -78,61 +77,50 @@ const projectXMenu = async () => {
|
|
|
78
77
|
|
|
79
78
|
// PropFirm selection box
|
|
80
79
|
console.log();
|
|
81
|
-
console.log(chalk.cyan('╔' + '═'.repeat(
|
|
82
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM',
|
|
83
|
-
console.log(chalk.cyan('
|
|
80
|
+
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
81
|
+
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (ProjectX)', W)) + chalk.cyan('║'));
|
|
82
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
84
83
|
|
|
85
|
-
// Display in
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
// Display in 2 columns
|
|
85
|
+
const menuRow = (left, right) => {
|
|
86
|
+
const leftPlain = left ? left.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
87
|
+
const rightPlain = right ? right.replace(/\x1b\[[0-9;]*m/g, '') : '';
|
|
88
|
+
const leftPadded = ' ' + (left || '') + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
89
|
+
const rightPadded = (right || '') + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
90
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
91
|
+
};
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
|
|
98
|
-
const textLen = 4 + 1 + item.name.length; // [XX] + space + name
|
|
99
|
-
const padding = colWidth - textLen - 2;
|
|
100
|
-
line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
|
|
101
|
-
} else {
|
|
102
|
-
line += ' '.repeat(colWidth);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// Adjust for exact width
|
|
106
|
-
const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
107
|
-
const adjust = innerWidth - lineLen;
|
|
108
|
-
console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
|
|
93
|
+
// Display propfirms in 2 columns
|
|
94
|
+
for (let i = 0; i < numbered.length; i += 2) {
|
|
95
|
+
const left = numbered[i];
|
|
96
|
+
const right = numbered[i + 1];
|
|
97
|
+
const leftText = chalk.cyan(`[${left.num.toString().padStart(2, ' ')}]`) + ' ' + chalk.white(left.name);
|
|
98
|
+
const rightText = right ? chalk.cyan(`[${right.num.toString().padStart(2, ' ')}]`) + ' ' + chalk.white(right.name) : '';
|
|
99
|
+
menuRow(leftText, rightText);
|
|
109
100
|
}
|
|
110
101
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
console.log(chalk.cyan('║') +
|
|
115
|
-
console.log(chalk.cyan('╚' + '═'.repeat(
|
|
102
|
+
// Back option
|
|
103
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
104
|
+
const backLine = ' ' + chalk.red('[X] Back') + ' '.repeat(W - 10);
|
|
105
|
+
console.log(chalk.cyan('║') + backLine + chalk.cyan('║'));
|
|
106
|
+
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
116
107
|
console.log();
|
|
117
108
|
|
|
118
|
-
const validInputs = numbered.map(n => n.num.toString());
|
|
119
|
-
validInputs.push('x', 'X');
|
|
120
|
-
|
|
121
109
|
const { action } = await inquirer.prompt([
|
|
122
110
|
{
|
|
123
111
|
type: 'input',
|
|
124
112
|
name: 'action',
|
|
125
|
-
message: chalk.cyan(
|
|
126
|
-
|
|
127
|
-
if (validInputs.includes(input)) return true;
|
|
128
|
-
return `Please enter 1-${numbered.length} or X`;
|
|
129
|
-
}
|
|
113
|
+
message: chalk.cyan('Select:'),
|
|
114
|
+
prefix: ''
|
|
130
115
|
}
|
|
131
116
|
]);
|
|
132
117
|
|
|
133
|
-
|
|
118
|
+
const input = (action || '').toLowerCase().trim();
|
|
119
|
+
if (input === 'x') return null;
|
|
120
|
+
|
|
121
|
+
const selectedIdx = parseInt(input) - 1;
|
|
122
|
+
if (isNaN(selectedIdx) || selectedIdx < 0 || selectedIdx >= numbered.length) return null;
|
|
134
123
|
|
|
135
|
-
const selectedIdx = parseInt(action) - 1;
|
|
136
124
|
const selectedPropfirm = numbered[selectedIdx];
|
|
137
125
|
|
|
138
126
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
@@ -345,53 +333,46 @@ const tradovateMenu = async () => {
|
|
|
345
333
|
*/
|
|
346
334
|
const addPropAccountMenu = async () => {
|
|
347
335
|
const boxWidth = getLogoWidth();
|
|
348
|
-
const
|
|
349
|
-
const col1Width = Math.floor(
|
|
350
|
-
const col2Width = innerWidth - col1Width;
|
|
336
|
+
const W = boxWidth - 2; // Inner width
|
|
337
|
+
const col1Width = Math.floor(W / 2);
|
|
351
338
|
|
|
352
339
|
console.log();
|
|
353
|
-
console.log(chalk.cyan('╔' + '═'.repeat(
|
|
354
|
-
console.log(chalk.cyan('║') + chalk.white.bold(centerText('ADD PROP ACCOUNT',
|
|
355
|
-
console.log(chalk.cyan('╠' + '═'.repeat(
|
|
340
|
+
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
341
|
+
console.log(chalk.cyan('║') + chalk.white.bold(centerText('ADD PROP ACCOUNT', W)) + chalk.cyan('║'));
|
|
342
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
356
343
|
|
|
357
344
|
const menuRow = (left, right) => {
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
const rightPad = col2Width - rightLen;
|
|
364
|
-
console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
|
|
345
|
+
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
346
|
+
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
347
|
+
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
348
|
+
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
349
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
365
350
|
};
|
|
366
351
|
|
|
367
352
|
menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
|
|
368
353
|
menuRow(chalk.cyan('[3] Tradovate'), chalk.red('[X] Back'));
|
|
369
354
|
|
|
370
|
-
console.log(chalk.cyan('╚' + '═'.repeat(
|
|
355
|
+
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
371
356
|
console.log();
|
|
372
357
|
|
|
373
358
|
const { action } = await inquirer.prompt([
|
|
374
359
|
{
|
|
375
360
|
type: 'input',
|
|
376
361
|
name: 'action',
|
|
377
|
-
message: chalk.cyan('
|
|
378
|
-
|
|
379
|
-
const valid = ['1', '2', '3', 'x', 'X'];
|
|
380
|
-
if (valid.includes(input)) return true;
|
|
381
|
-
return 'Please enter 1, 2, 3 or X';
|
|
382
|
-
}
|
|
362
|
+
message: chalk.cyan('Select:'),
|
|
363
|
+
prefix: ''
|
|
383
364
|
}
|
|
384
365
|
]);
|
|
385
366
|
|
|
367
|
+
const input = (action || '').toLowerCase().trim();
|
|
386
368
|
const actionMap = {
|
|
387
369
|
'1': 'projectx',
|
|
388
370
|
'2': 'rithmic',
|
|
389
371
|
'3': 'tradovate',
|
|
390
|
-
'x': null
|
|
391
|
-
'X': null
|
|
372
|
+
'x': null
|
|
392
373
|
};
|
|
393
374
|
|
|
394
|
-
return actionMap[
|
|
375
|
+
return actionMap[input] || null;
|
|
395
376
|
};
|
|
396
377
|
|
|
397
378
|
module.exports = {
|
package/src/menus/dashboard.js
CHANGED
|
@@ -10,6 +10,7 @@ const { execSync, spawn } = require('child_process');
|
|
|
10
10
|
|
|
11
11
|
const { connections } = require('../services');
|
|
12
12
|
const { getLogoWidth, centerText, prepareStdin } = require('../ui');
|
|
13
|
+
const { getCachedStats } = require('../app');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Dashboard menu after login
|
|
@@ -19,115 +20,116 @@ const dashboardMenu = async (service) => {
|
|
|
19
20
|
// Ensure stdin is ready for prompts
|
|
20
21
|
prepareStdin();
|
|
21
22
|
|
|
22
|
-
const user = service.user;
|
|
23
23
|
const boxWidth = getLogoWidth();
|
|
24
|
-
const W = boxWidth - 2; //
|
|
24
|
+
const W = boxWidth - 2; // Inner width (without borders)
|
|
25
25
|
|
|
26
|
-
// Helper to
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
// Helper to create a line that fits exactly in the box
|
|
27
|
+
const makeLine = (content, align = 'left') => {
|
|
28
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
29
|
+
const padding = W - plainLen;
|
|
30
|
+
if (align === 'center') {
|
|
31
|
+
const leftPad = Math.floor(padding / 2);
|
|
32
|
+
const rightPad = padding - leftPad;
|
|
33
|
+
return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + chalk.cyan('║');
|
|
34
|
+
}
|
|
35
|
+
return chalk.cyan('║') + content + ' '.repeat(Math.max(0, padding)) + chalk.cyan('║');
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
// Dashboard box header
|
|
38
39
|
console.log();
|
|
39
40
|
console.log(chalk.cyan('╔' + '═'.repeat(W) + '╗'));
|
|
40
|
-
console.log(
|
|
41
|
+
console.log(makeLine(chalk.yellow.bold('Welcome, HQX Trader!'), 'center'));
|
|
41
42
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
+
// Show connected propfirms centered on one line (max 3)
|
|
44
45
|
const allConns = connections.getAll();
|
|
45
46
|
if (allConns.length > 0) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
47
|
+
const propfirms = allConns.slice(0, 3).map(c => c.propfirm || c.type || 'Connected');
|
|
48
|
+
const propfirmText = propfirms.map(p => chalk.green('● ') + chalk.white(p)).join(' ');
|
|
49
|
+
console.log(makeLine(propfirmText, 'center'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Show stats bar (Connections, Accounts, Balance, P&L)
|
|
53
|
+
let statsInfo = null;
|
|
54
|
+
try { statsInfo = getCachedStats(); } catch (e) {}
|
|
55
|
+
|
|
56
|
+
if (statsInfo) {
|
|
57
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const totalGaps = (numBoxes - 1) * gap;
|
|
53
|
-
const connBoxWidth = Math.floor((W - totalGaps - 2) / numBoxes); // -2 for outer padding
|
|
59
|
+
const connStr = `Connections: ${statsInfo.connections}`;
|
|
60
|
+
const accStr = `Accounts: ${statsInfo.accounts}`;
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
console.log(chalk.cyan('║') + chalk.green(topLine) + ' '.repeat(Math.max(0, topPad)) + chalk.cyan('║'));
|
|
69
|
-
|
|
70
|
-
// Content of boxes
|
|
71
|
-
let contentLine = ' ';
|
|
72
|
-
for (let i = 0; i < numInRow; i++) {
|
|
73
|
-
const connText = rowConns[i].propfirm || rowConns[i].type || 'Connected';
|
|
74
|
-
const truncated = connText.length > rowBoxWidth - 4 ? connText.slice(0, rowBoxWidth - 7) + '...' : connText;
|
|
75
|
-
const innerWidth = rowBoxWidth - 4; // -2 for borders, -2 for padding
|
|
76
|
-
const textPad = Math.floor((innerWidth - truncated.length) / 2);
|
|
77
|
-
const textPadRight = innerWidth - truncated.length - textPad;
|
|
78
|
-
contentLine += '│ ' + ' '.repeat(textPad) + truncated + ' '.repeat(textPadRight) + ' │';
|
|
79
|
-
if (i < numInRow - 1) contentLine += ' '.repeat(gap);
|
|
80
|
-
}
|
|
81
|
-
const contentPad = W - contentLine.length;
|
|
82
|
-
console.log(chalk.cyan('║') + chalk.green(contentLine) + ' '.repeat(Math.max(0, contentPad)) + chalk.cyan('║'));
|
|
83
|
-
|
|
84
|
-
// Bottom border of boxes
|
|
85
|
-
let bottomLine = ' ';
|
|
86
|
-
for (let i = 0; i < numInRow; i++) {
|
|
87
|
-
bottomLine += '└' + '─'.repeat(rowBoxWidth - 2) + '┘';
|
|
88
|
-
if (i < numInRow - 1) bottomLine += ' '.repeat(gap);
|
|
89
|
-
}
|
|
90
|
-
const bottomPad = W - bottomLine.length;
|
|
91
|
-
console.log(chalk.cyan('║') + chalk.green(bottomLine) + ' '.repeat(Math.max(0, bottomPad)) + chalk.cyan('║'));
|
|
62
|
+
const balStr = statsInfo.balance !== null
|
|
63
|
+
? `$${statsInfo.balance.toLocaleString()}`
|
|
64
|
+
: '--';
|
|
65
|
+
const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
|
|
66
|
+
|
|
67
|
+
let pnlDisplay, pnlColor;
|
|
68
|
+
if (statsInfo.pnl !== null) {
|
|
69
|
+
const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
|
|
70
|
+
pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
71
|
+
pnlDisplay = `${pnlSign}$${Math.abs(statsInfo.pnl).toLocaleString()}`;
|
|
72
|
+
} else {
|
|
73
|
+
pnlColor = chalk.gray;
|
|
74
|
+
pnlDisplay = '--';
|
|
92
75
|
}
|
|
76
|
+
|
|
77
|
+
const statsText = connStr + ' ' + accStr + ' Balance: ' + balStr + ' P&L: ' + pnlDisplay;
|
|
78
|
+
const statsPlain = `${connStr} ${accStr} Balance: ${balStr} P&L: ${pnlDisplay}`;
|
|
79
|
+
const statsLeftPad = Math.floor((W - statsPlain.length) / 2);
|
|
80
|
+
const statsRightPad = W - statsPlain.length - statsLeftPad;
|
|
81
|
+
|
|
82
|
+
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
83
|
+
chalk.white(connStr) + ' ' +
|
|
84
|
+
chalk.white(accStr) + ' ' +
|
|
85
|
+
chalk.white('Balance: ') + balColor(balStr) + ' ' +
|
|
86
|
+
chalk.white('P&L: ') + pnlColor(pnlDisplay) +
|
|
87
|
+
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
96
91
|
|
|
97
92
|
// Menu options in 2 columns
|
|
98
93
|
const col1Width = Math.floor(W / 2);
|
|
99
|
-
const col2Width = W - col1Width;
|
|
100
94
|
|
|
101
95
|
const menuRow = (left, right) => {
|
|
102
96
|
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '');
|
|
103
|
-
const rightPlain = right
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
console.log(chalk.cyan('║') +
|
|
97
|
+
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '');
|
|
98
|
+
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain.length - 2));
|
|
99
|
+
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain.length));
|
|
100
|
+
console.log(chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║'));
|
|
107
101
|
};
|
|
108
102
|
|
|
103
|
+
// Display menu items in 2 columns inside the box
|
|
104
|
+
menuRow(chalk.cyan('[1] View Accounts'), chalk.cyan('[2] View Stats'));
|
|
105
|
+
menuRow(chalk.cyan('[+] Add Prop-Account'), chalk.magenta('[A] Algo-Trading'));
|
|
106
|
+
menuRow(chalk.yellow('[U] Update HQX'), chalk.red('[X] Disconnect'));
|
|
107
|
+
|
|
109
108
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
110
109
|
console.log();
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
const {
|
|
110
|
+
|
|
111
|
+
// Input prompt
|
|
112
|
+
const { choice } = await inquirer.prompt([
|
|
114
113
|
{
|
|
115
|
-
type: '
|
|
116
|
-
name: '
|
|
117
|
-
message: chalk.cyan('Select
|
|
118
|
-
|
|
119
|
-
{ name: chalk.cyan('[1] View Accounts'), value: 'accounts' },
|
|
120
|
-
{ name: chalk.cyan('[2] View Stats'), value: 'stats' },
|
|
121
|
-
{ name: chalk.cyan('[+] Add Prop-Account'), value: 'add_prop_account' },
|
|
122
|
-
{ name: chalk.magenta('[A] Algo-Trading'), value: 'algotrading' },
|
|
123
|
-
{ name: chalk.yellow('[U] Update HQX'), value: 'update' },
|
|
124
|
-
{ name: chalk.red('[X] Disconnect'), value: 'disconnect' }
|
|
125
|
-
],
|
|
126
|
-
loop: false
|
|
114
|
+
type: 'input',
|
|
115
|
+
name: 'choice',
|
|
116
|
+
message: chalk.cyan('Select:'),
|
|
117
|
+
prefix: ''
|
|
127
118
|
}
|
|
128
119
|
]);
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
|
|
121
|
+
// Map input to action
|
|
122
|
+
const input = (choice || '').toString().toLowerCase().trim();
|
|
123
|
+
const actionMap = {
|
|
124
|
+
'1': 'accounts',
|
|
125
|
+
'2': 'stats',
|
|
126
|
+
'+': 'add_prop_account',
|
|
127
|
+
'a': 'algotrading',
|
|
128
|
+
'u': 'update',
|
|
129
|
+
'x': 'disconnect'
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return actionMap[input] || null;
|
|
131
133
|
};
|
|
132
134
|
|
|
133
135
|
/**
|
|
@@ -188,43 +190,19 @@ const handleUpdate = async () => {
|
|
|
188
190
|
|
|
189
191
|
// Compare versions
|
|
190
192
|
if (currentVersion === latestVersion) {
|
|
191
|
-
spinner.succeed(
|
|
192
|
-
console.log();
|
|
193
|
-
console.log(chalk.green(` You have the latest version: v${currentVersion}`));
|
|
194
|
-
console.log();
|
|
195
|
-
await waitForEnter();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Ask user before updating
|
|
200
|
-
spinner.stop();
|
|
201
|
-
console.log();
|
|
202
|
-
console.log(chalk.cyan(` Current version: v${currentVersion}`));
|
|
203
|
-
console.log(chalk.green(` Latest version: v${latestVersion}`));
|
|
204
|
-
console.log();
|
|
205
|
-
|
|
206
|
-
prepareStdin();
|
|
207
|
-
const { confirm } = await inquirer.prompt([{
|
|
208
|
-
type: 'confirm',
|
|
209
|
-
name: 'confirm',
|
|
210
|
-
message: 'Do you want to update now?',
|
|
211
|
-
default: true
|
|
212
|
-
}]);
|
|
213
|
-
|
|
214
|
-
if (!confirm) {
|
|
215
|
-
console.log(chalk.gray(' Update cancelled'));
|
|
193
|
+
spinner.succeed(`Already up to date! (v${currentVersion})`);
|
|
216
194
|
console.log();
|
|
217
|
-
await
|
|
195
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
218
196
|
return;
|
|
219
197
|
}
|
|
220
198
|
|
|
221
|
-
//
|
|
222
|
-
spinner =
|
|
199
|
+
// Show version info and update automatically
|
|
200
|
+
spinner.text = `Updating v${currentVersion} → v${latestVersion}...`;
|
|
223
201
|
|
|
224
202
|
try {
|
|
225
203
|
execSync('npm install -g hedgequantx@latest 2>/dev/null', {
|
|
226
204
|
stdio: 'pipe',
|
|
227
|
-
timeout: 120000,
|
|
205
|
+
timeout: 120000,
|
|
228
206
|
encoding: 'utf8'
|
|
229
207
|
});
|
|
230
208
|
} catch (e) {
|
|
@@ -233,52 +211,29 @@ const handleUpdate = async () => {
|
|
|
233
211
|
console.log(chalk.yellow(' Try manually:'));
|
|
234
212
|
console.log(chalk.white(' npm install -g hedgequantx@latest'));
|
|
235
213
|
console.log();
|
|
236
|
-
if (e.message) {
|
|
237
|
-
console.log(chalk.gray(` Error: ${e.message.substring(0, 100)}`));
|
|
238
|
-
console.log();
|
|
239
|
-
}
|
|
240
214
|
await waitForEnter();
|
|
241
215
|
return;
|
|
242
216
|
}
|
|
243
217
|
|
|
244
|
-
spinner.succeed(
|
|
218
|
+
spinner.succeed(`Updated: v${currentVersion} → v${latestVersion}`);
|
|
245
219
|
console.log();
|
|
246
|
-
console.log(chalk.
|
|
220
|
+
console.log(chalk.cyan(' Restarting HedgeQuantX CLI...'));
|
|
247
221
|
console.log();
|
|
248
222
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
const { restart } = await inquirer.prompt([{
|
|
252
|
-
type: 'confirm',
|
|
253
|
-
name: 'restart',
|
|
254
|
-
message: 'Restart HQX now?',
|
|
255
|
-
default: true
|
|
256
|
-
}]);
|
|
223
|
+
// Auto restart after 2 seconds
|
|
224
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
257
225
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
stdio: 'inherit',
|
|
270
|
-
detached: true,
|
|
271
|
-
shell: true
|
|
272
|
-
});
|
|
273
|
-
child.unref();
|
|
274
|
-
process.exit(0);
|
|
275
|
-
} catch (e) {
|
|
276
|
-
console.log(chalk.yellow(' Could not auto-restart. Please run: hedgequantx'));
|
|
277
|
-
console.log();
|
|
278
|
-
await waitForEnter();
|
|
279
|
-
}
|
|
280
|
-
} else {
|
|
281
|
-
console.log(chalk.gray(' Run "hedgequantx" to use the new version'));
|
|
226
|
+
// Restart the CLI
|
|
227
|
+
try {
|
|
228
|
+
const child = spawn('hedgequantx', [], {
|
|
229
|
+
stdio: 'inherit',
|
|
230
|
+
detached: true,
|
|
231
|
+
shell: true
|
|
232
|
+
});
|
|
233
|
+
child.unref();
|
|
234
|
+
process.exit(0);
|
|
235
|
+
} catch (e) {
|
|
236
|
+
console.log(chalk.yellow(' Could not auto-restart. Please run: hedgequantx'));
|
|
282
237
|
console.log();
|
|
283
238
|
await waitForEnter();
|
|
284
239
|
}
|
|
@@ -173,12 +173,16 @@ const copyTradingMenu = async () => {
|
|
|
173
173
|
const maxRisk = parseInt(maxRiskInput) || 200;
|
|
174
174
|
|
|
175
175
|
// Step 6: Privacy
|
|
176
|
-
const {
|
|
177
|
-
type: '
|
|
178
|
-
name: '
|
|
179
|
-
message: '
|
|
180
|
-
|
|
176
|
+
const { privacyChoice } = await inquirer.prompt([{
|
|
177
|
+
type: 'list',
|
|
178
|
+
name: 'privacyChoice',
|
|
179
|
+
message: 'Account names:',
|
|
180
|
+
choices: [
|
|
181
|
+
{ name: 'Hide account names', value: false },
|
|
182
|
+
{ name: 'Show account names', value: true }
|
|
183
|
+
]
|
|
181
184
|
}]);
|
|
185
|
+
const showNames = privacyChoice;
|
|
182
186
|
|
|
183
187
|
// Confirm
|
|
184
188
|
console.log();
|
|
@@ -283,9 +287,12 @@ const launchCopyTrading = async (config) => {
|
|
|
283
287
|
|
|
284
288
|
// Combined stats
|
|
285
289
|
const stats = {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
290
|
+
leadName,
|
|
291
|
+
followerName,
|
|
292
|
+
leadSymbol: lead.symbol.name,
|
|
293
|
+
followerSymbol: follower.symbol.name,
|
|
294
|
+
leadQty: lead.contracts,
|
|
295
|
+
followerQty: follower.contracts,
|
|
289
296
|
target: dailyTarget,
|
|
290
297
|
risk: maxRisk,
|
|
291
298
|
pnl: 0,
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -146,37 +146,55 @@ class AlgoUI {
|
|
|
146
146
|
|
|
147
147
|
this._line(chalk.cyan(GT));
|
|
148
148
|
|
|
149
|
-
// Row 1: Account |
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const qtyStr = stats.contracts || '1/1';
|
|
149
|
+
// Row 1: Lead Account | Follower Account
|
|
150
|
+
const leadName = (stats.leadName || stats.accountName || 'N/A').substring(0, 40);
|
|
151
|
+
const followerName = (stats.followerName || 'N/A').substring(0, 40);
|
|
153
152
|
|
|
154
|
-
const r1c1 = buildCell('
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
row(r1c1.padded, r1c2t + pad(Math.max(0, colR - r1c2p.length)));
|
|
153
|
+
const r1c1 = buildCell('Lead', leadName, chalk.cyan, colL);
|
|
154
|
+
const r1c2 = buildCell('Follower', followerName, chalk.magenta, colR);
|
|
155
|
+
row(r1c1.padded, r1c2.padded);
|
|
158
156
|
|
|
159
157
|
this._line(chalk.cyan(GM));
|
|
160
158
|
|
|
161
|
-
// Row 2:
|
|
162
|
-
const
|
|
163
|
-
const
|
|
159
|
+
// Row 2: Lead Symbol | Follower Symbol
|
|
160
|
+
const leadSymbol = (stats.leadSymbol || stats.symbol || 'N/A').substring(0, 35);
|
|
161
|
+
const followerSymbol = (stats.followerSymbol || 'N/A').substring(0, 35);
|
|
162
|
+
|
|
163
|
+
const r2c1 = buildCell('Symbol', leadSymbol, chalk.yellow, colL);
|
|
164
|
+
const r2c2 = buildCell('Symbol', followerSymbol, chalk.yellow, colR);
|
|
164
165
|
row(r2c1.padded, r2c2.padded);
|
|
165
166
|
|
|
166
167
|
this._line(chalk.cyan(GM));
|
|
167
168
|
|
|
168
|
-
// Row 3:
|
|
169
|
-
const
|
|
170
|
-
const
|
|
169
|
+
// Row 3: Lead Qty | Follower Qty
|
|
170
|
+
const leadQty = stats.leadQty || '1';
|
|
171
|
+
const followerQty = stats.followerQty || '1';
|
|
172
|
+
|
|
173
|
+
const r3c1 = buildCell('Qty', leadQty.toString(), chalk.cyan, colL);
|
|
174
|
+
const r3c2 = buildCell('Qty', followerQty.toString(), chalk.cyan, colR);
|
|
171
175
|
row(r3c1.padded, r3c2.padded);
|
|
172
176
|
|
|
173
177
|
this._line(chalk.cyan(GM));
|
|
174
178
|
|
|
175
|
-
// Row 4:
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
// Row 4: Target | Risk
|
|
180
|
+
const r4c1 = buildCell('Target', '$' + (stats.target || 0).toFixed(2), chalk.green, colL);
|
|
181
|
+
const r4c2 = buildCell('Risk', '$' + (stats.risk || 0).toFixed(2), chalk.red, colR);
|
|
182
|
+
row(r4c1.padded, r4c2.padded);
|
|
183
|
+
|
|
184
|
+
this._line(chalk.cyan(GM));
|
|
185
|
+
|
|
186
|
+
// Row 5: P&L | Server
|
|
187
|
+
const r5c1 = buildCell('P&L', pnlStr, pnlColor, colL);
|
|
188
|
+
const r5c2 = buildCell('Server', stats.connected ? 'ON' : 'OFF', serverColor, colR);
|
|
189
|
+
row(r5c1.padded, r5c2.padded);
|
|
190
|
+
|
|
191
|
+
this._line(chalk.cyan(GM));
|
|
192
|
+
|
|
193
|
+
// Row 6: Trades | Latency
|
|
194
|
+
const r6c1t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
|
|
195
|
+
const r6c1p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
196
|
+
const r6c2 = buildCell('Latency', `${stats.latency || 0}ms`, latencyColor, colR);
|
|
197
|
+
row(r6c1t + pad(colL - r6c1p.length), r6c2.padded);
|
|
180
198
|
|
|
181
199
|
this._line(chalk.cyan(GB));
|
|
182
200
|
}
|
|
@@ -445,31 +445,11 @@ class ProjectXService {
|
|
|
445
445
|
|
|
446
446
|
/**
|
|
447
447
|
* Get popular contracts for trading
|
|
448
|
+
* Uses shared contract list for consistency with Rithmic
|
|
448
449
|
*/
|
|
449
450
|
async getContracts() {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const symbols = ['ES', 'NQ', 'MES', 'MNQ', 'CL', 'GC', 'RTY', 'YM'];
|
|
453
|
-
const allContracts = [];
|
|
454
|
-
|
|
455
|
-
for (const sym of symbols) {
|
|
456
|
-
const response = await this._request(
|
|
457
|
-
this.propfirm.gatewayApi, '/api/Contract/search', 'POST',
|
|
458
|
-
{ searchText: sym, live: false }
|
|
459
|
-
);
|
|
460
|
-
if (response.statusCode === 200) {
|
|
461
|
-
const contracts = response.data.contracts || response.data || [];
|
|
462
|
-
// Take first contract for each symbol (front month)
|
|
463
|
-
if (contracts.length > 0) {
|
|
464
|
-
allContracts.push(contracts[0]);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return { success: true, contracts: allContracts };
|
|
470
|
-
} catch (error) {
|
|
471
|
-
return { success: false, contracts: [], error: error.message };
|
|
472
|
-
}
|
|
451
|
+
const { getContractsWithMonthCode } = require('../../config/contracts');
|
|
452
|
+
return { success: true, contracts: getContractsWithMonthCode() };
|
|
473
453
|
}
|
|
474
454
|
|
|
475
455
|
async searchContracts(searchText) {
|
|
@@ -210,31 +210,14 @@ class RithmicService extends EventEmitter {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
// All available contracts for Rithmic
|
|
214
|
-
_getAvailableContracts() {
|
|
215
|
-
return [
|
|
216
|
-
{ symbol: 'ESH5', name: 'E-mini S&P 500 Mar 2025', exchange: 'CME', group: 'Index' },
|
|
217
|
-
{ symbol: 'NQH5', name: 'E-mini NASDAQ-100 Mar 2025', exchange: 'CME', group: 'Index' },
|
|
218
|
-
{ symbol: 'MESH5', name: 'Micro E-mini S&P 500 Mar 2025', exchange: 'CME', group: 'Micro' },
|
|
219
|
-
{ symbol: 'MNQH5', name: 'Micro E-mini NASDAQ-100 Mar 2025', exchange: 'CME', group: 'Micro' },
|
|
220
|
-
{ symbol: 'MCLE5', name: 'Micro Crude Oil Mar 2025', exchange: 'NYMEX', group: 'Micro' },
|
|
221
|
-
{ symbol: 'MGCG5', name: 'Micro Gold Feb 2025', exchange: 'COMEX', group: 'Micro' },
|
|
222
|
-
{ symbol: 'CLH5', name: 'Crude Oil Mar 2025', exchange: 'NYMEX', group: 'Energy' },
|
|
223
|
-
{ symbol: 'GCG5', name: 'Gold Feb 2025', exchange: 'COMEX', group: 'Metals' },
|
|
224
|
-
{ symbol: 'SIH5', name: 'Silver Mar 2025', exchange: 'COMEX', group: 'Metals' },
|
|
225
|
-
{ symbol: 'RTYH5', name: 'E-mini Russell 2000 Mar 2025', exchange: 'CME', group: 'Index' },
|
|
226
|
-
{ symbol: 'YMH5', name: 'E-mini Dow Jones Mar 2025', exchange: 'CBOT', group: 'Index' },
|
|
227
|
-
{ symbol: 'ZBH5', name: '30-Year US Treasury Bond Mar 2025', exchange: 'CBOT', group: 'Bonds' },
|
|
228
|
-
{ symbol: 'ZNH5', name: '10-Year US Treasury Note Mar 2025', exchange: 'CBOT', group: 'Bonds' },
|
|
229
|
-
];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
213
|
async getContracts() {
|
|
233
|
-
|
|
214
|
+
const { getContractsWithMonthCode } = require('../../config/contracts');
|
|
215
|
+
return { success: true, contracts: getContractsWithMonthCode() };
|
|
234
216
|
}
|
|
235
217
|
|
|
236
218
|
async searchContracts(searchText) {
|
|
237
|
-
const
|
|
219
|
+
const { getContractsWithMonthCode } = require('../../config/contracts');
|
|
220
|
+
const contracts = getContractsWithMonthCode();
|
|
238
221
|
if (!searchText) return contracts;
|
|
239
222
|
const search = searchText.toUpperCase();
|
|
240
223
|
return contracts.filter(c => c.symbol.includes(search) || c.name.toUpperCase().includes(search));
|