hedgequantx 1.5.8 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.5.8",
3
+ "version": "1.5.9",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
5
  "main": "src/app.js",
6
6
  "bin": {
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
+ };
@@ -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
@@ -48,6 +49,44 @@ const dashboardMenu = async (service) => {
48
49
  console.log(makeLine(propfirmText, 'center'));
49
50
  }
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) + '╣'));
58
+
59
+ const connStr = `Connections: ${statsInfo.connections}`;
60
+ const accStr = `Accounts: ${statsInfo.accounts}`;
61
+
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 = '--';
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('║'));
88
+ }
89
+
51
90
  console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
52
91
 
53
92
  // Menu options in 2 columns
@@ -173,12 +173,16 @@ const copyTradingMenu = async () => {
173
173
  const maxRisk = parseInt(maxRiskInput) || 200;
174
174
 
175
175
  // Step 6: Privacy
176
- const { showNames } = await inquirer.prompt([{
177
- type: 'confirm',
178
- name: 'showNames',
179
- message: 'Show account names?',
180
- default: false
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
- accountName: `${leadName} -> ${followerName}`,
287
- symbol: `${lead.symbol.name} / ${follower.symbol.name}`,
288
- contracts: `${lead.contracts}/${follower.contracts}`,
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,
@@ -146,37 +146,55 @@ class AlgoUI {
146
146
 
147
147
  this._line(chalk.cyan(GT));
148
148
 
149
- // Row 1: Account | Symbol (truncate long values)
150
- const accName = (stats.accountName || 'N/A').substring(0, 35);
151
- const symName = (stats.symbol || 'N/A').substring(0, 25);
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('Account', accName, chalk.cyan, colL);
155
- const r1c2t = ` Symbol: ${chalk.yellow(symName)} Qty: ${chalk.cyan(qtyStr)}`;
156
- const r1c2p = ` Symbol: ${symName} Qty: ${qtyStr}`;
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: Target | Risk
162
- const r2c1 = buildCell('Target', '$' + (stats.target || 0).toFixed(2), chalk.green, colL);
163
- const r2c2 = buildCell('Risk', '$' + (stats.risk || 0).toFixed(2), chalk.red, colR);
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: P&L | Server
169
- const r3c1 = buildCell('P&L', pnlStr, pnlColor, colL);
170
- const r3c2 = buildCell('Server', stats.connected ? 'ON' : 'OFF', serverColor, colR);
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: Trades | Latency
176
- const r4c1t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
177
- const r4c1p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
178
- const r4c2 = buildCell('Latency', `${stats.latency || 0}ms`, latencyColor, colR);
179
- row(r4c1t + pad(colL - r4c1p.length), r4c2.padded);
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,76 +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
- try {
451
- // Search for popular futures symbols
452
- const symbols = ['ES', 'NQ', 'MES', 'MNQ', 'CL', 'GC', 'RTY', 'YM', 'SI', 'ZB', 'ZN', 'NG'];
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
- const contract = contracts[0];
465
- // Ensure name is set properly
466
- if (!contract.name || contract.name === contract.symbol) {
467
- contract.name = this._getContractDisplayName(contract.symbol) || contract.symbol;
468
- }
469
- allContracts.push(contract);
470
- }
471
- }
472
- }
473
-
474
- return { success: true, contracts: allContracts };
475
- } catch (error) {
476
- return { success: false, contracts: [], error: error.message };
477
- }
478
- }
479
-
480
- /**
481
- * Get display name for contract symbol
482
- */
483
- _getContractDisplayName(symbol) {
484
- const baseSymbol = symbol.replace(/[A-Z][0-9]$/, '').replace(/[0-9]+$/, '');
485
- const names = {
486
- 'ES': 'E-mini S&P 500',
487
- 'NQ': 'E-mini NASDAQ-100',
488
- 'MES': 'Micro E-mini S&P 500',
489
- 'MNQ': 'Micro E-mini NASDAQ-100',
490
- 'RTY': 'E-mini Russell 2000',
491
- 'M2K': 'Micro E-mini Russell 2000',
492
- 'YM': 'E-mini Dow Jones',
493
- 'MYM': 'Micro E-mini Dow Jones',
494
- 'CL': 'Crude Oil',
495
- 'MCL': 'Micro Crude Oil',
496
- 'GC': 'Gold',
497
- 'MGC': 'Micro Gold',
498
- 'SI': 'Silver',
499
- 'SIL': 'Micro Silver',
500
- 'NG': 'Natural Gas',
501
- 'ZB': '30-Year Treasury Bond',
502
- 'ZN': '10-Year Treasury Note',
503
- 'ZF': '5-Year Treasury Note',
504
- 'ZC': 'Corn',
505
- 'ZS': 'Soybeans',
506
- 'ZW': 'Wheat',
507
- '6E': 'Euro FX',
508
- '6B': 'British Pound',
509
- '6J': 'Japanese Yen',
510
- };
511
- const baseName = names[baseSymbol];
512
- if (baseName) {
513
- // Extract month/year from symbol
514
- const monthYear = symbol.slice(baseSymbol.length);
515
- return `${baseName} (${monthYear})`;
516
- }
517
- return symbol;
451
+ const { getContractsWithMonthCode } = require('../../config/contracts');
452
+ return { success: true, contracts: getContractsWithMonthCode() };
518
453
  }
519
454
 
520
455
  async searchContracts(searchText) {
@@ -210,69 +210,14 @@ class RithmicService extends EventEmitter {
210
210
  };
211
211
  }
212
212
 
213
- // All available contracts for Rithmic
214
- // TODO: Fetch from TICKER_PLANT API instead of static list
215
- _getAvailableContracts() {
216
- // Current front-month contracts (update monthly)
217
- return [
218
- // Index Futures
219
- { symbol: 'ESH5', name: 'E-mini S&P 500 (Mar 25)', exchange: 'CME', group: 'Index' },
220
- { symbol: 'NQH5', name: 'E-mini NASDAQ-100 (Mar 25)', exchange: 'CME', group: 'Index' },
221
- { symbol: 'RTYH5', name: 'E-mini Russell 2000 (Mar 25)', exchange: 'CME', group: 'Index' },
222
- { symbol: 'YMH5', name: 'E-mini Dow Jones (Mar 25)', exchange: 'CBOT', group: 'Index' },
223
-
224
- // Micro Index Futures
225
- { symbol: 'MESH5', name: 'Micro E-mini S&P 500 (Mar 25)', exchange: 'CME', group: 'Micro Index' },
226
- { symbol: 'MNQH5', name: 'Micro E-mini NASDAQ-100 (Mar 25)', exchange: 'CME', group: 'Micro Index' },
227
- { symbol: 'M2KH5', name: 'Micro E-mini Russell 2000 (Mar 25)', exchange: 'CME', group: 'Micro Index' },
228
- { symbol: 'MYMH5', name: 'Micro E-mini Dow Jones (Mar 25)', exchange: 'CBOT', group: 'Micro Index' },
229
-
230
- // Energy Futures
231
- { symbol: 'CLG5', name: 'Crude Oil (Feb 25)', exchange: 'NYMEX', group: 'Energy' },
232
- { symbol: 'CLH5', name: 'Crude Oil (Mar 25)', exchange: 'NYMEX', group: 'Energy' },
233
- { symbol: 'NGG5', name: 'Natural Gas (Feb 25)', exchange: 'NYMEX', group: 'Energy' },
234
- { symbol: 'NGH5', name: 'Natural Gas (Mar 25)', exchange: 'NYMEX', group: 'Energy' },
235
-
236
- // Micro Energy
237
- { symbol: 'MCLG5', name: 'Micro Crude Oil (Feb 25)', exchange: 'NYMEX', group: 'Micro Energy' },
238
- { symbol: 'MCLH5', name: 'Micro Crude Oil (Mar 25)', exchange: 'NYMEX', group: 'Micro Energy' },
239
-
240
- // Metals Futures
241
- { symbol: 'GCG5', name: 'Gold (Feb 25)', exchange: 'COMEX', group: 'Metals' },
242
- { symbol: 'GCJ5', name: 'Gold (Apr 25)', exchange: 'COMEX', group: 'Metals' },
243
- { symbol: 'SIH5', name: 'Silver (Mar 25)', exchange: 'COMEX', group: 'Metals' },
244
- { symbol: 'HGH5', name: 'Copper (Mar 25)', exchange: 'COMEX', group: 'Metals' },
245
-
246
- // Micro Metals
247
- { symbol: 'MGCG5', name: 'Micro Gold (Feb 25)', exchange: 'COMEX', group: 'Micro Metals' },
248
- { symbol: 'MGCJ5', name: 'Micro Gold (Apr 25)', exchange: 'COMEX', group: 'Micro Metals' },
249
- { symbol: 'SILU5', name: 'Micro Silver (Sep 25)', exchange: 'COMEX', group: 'Micro Metals' },
250
-
251
- // Treasury Futures
252
- { symbol: 'ZBH5', name: '30-Year US Treasury Bond (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
253
- { symbol: 'ZNH5', name: '10-Year US Treasury Note (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
254
- { symbol: 'ZFH5', name: '5-Year US Treasury Note (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
255
- { symbol: 'ZTH5', name: '2-Year US Treasury Note (Mar 25)', exchange: 'CBOT', group: 'Bonds' },
256
-
257
- // Agriculture Futures
258
- { symbol: 'ZCH5', name: 'Corn (Mar 25)', exchange: 'CBOT', group: 'Agriculture' },
259
- { symbol: 'ZSH5', name: 'Soybeans (Mar 25)', exchange: 'CBOT', group: 'Agriculture' },
260
- { symbol: 'ZWH5', name: 'Wheat (Mar 25)', exchange: 'CBOT', group: 'Agriculture' },
261
-
262
- // Currency Futures
263
- { symbol: '6EH5', name: 'Euro FX (Mar 25)', exchange: 'CME', group: 'Currency' },
264
- { symbol: '6BH5', name: 'British Pound (Mar 25)', exchange: 'CME', group: 'Currency' },
265
- { symbol: '6JH5', name: 'Japanese Yen (Mar 25)', exchange: 'CME', group: 'Currency' },
266
- { symbol: '6AH5', name: 'Australian Dollar (Mar 25)', exchange: 'CME', group: 'Currency' },
267
- ];
268
- }
269
-
270
213
  async getContracts() {
271
- return { success: true, contracts: this._getAvailableContracts() };
214
+ const { getContractsWithMonthCode } = require('../../config/contracts');
215
+ return { success: true, contracts: getContractsWithMonthCode() };
272
216
  }
273
217
 
274
218
  async searchContracts(searchText) {
275
- const contracts = this._getAvailableContracts();
219
+ const { getContractsWithMonthCode } = require('../../config/contracts');
220
+ const contracts = getContractsWithMonthCode();
276
221
  if (!searchText) return contracts;
277
222
  const search = searchText.toUpperCase();
278
223
  return contracts.filter(c => c.symbol.includes(search) || c.name.toUpperCase().includes(search));