hedgequantx 1.5.8 → 1.5.10
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 +5 -48
- package/src/config/contracts.js +100 -0
- package/src/menus/dashboard.js +39 -1
- package/src/pages/algo/copy-trading.js +15 -8
- package/src/pages/algo/ui.js +37 -19
- package/src/services/projectx/index.js +3 -68
- package/src/services/rithmic/index.js +4 -59
- package/src/services/stats-cache.js +23 -0
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -10,6 +10,7 @@ const ora = require('ora');
|
|
|
10
10
|
const { connections } = require('./services');
|
|
11
11
|
const { getLogoWidth, centerText, prepareStdin } = require('./ui');
|
|
12
12
|
const { logger } = require('./utils');
|
|
13
|
+
const { setCachedStats, clearCachedStats } = require('./services/stats-cache');
|
|
13
14
|
|
|
14
15
|
const log = logger.scope('App');
|
|
15
16
|
|
|
@@ -25,9 +26,6 @@ const { projectXMenu, rithmicMenu, tradovateMenu, addPropAccountMenu, dashboardM
|
|
|
25
26
|
let currentService = null;
|
|
26
27
|
let currentPlatform = null; // 'projectx' or 'rithmic'
|
|
27
28
|
|
|
28
|
-
// Cached stats for banner (avoid refetching on every screen)
|
|
29
|
-
let cachedStats = null;
|
|
30
|
-
|
|
31
29
|
/**
|
|
32
30
|
* Global terminal restoration - ensures terminal is always restored on exit
|
|
33
31
|
*/
|
|
@@ -92,18 +90,18 @@ const refreshStats = async () => {
|
|
|
92
90
|
}
|
|
93
91
|
});
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
setCachedStats({
|
|
96
94
|
connections: connections.count(),
|
|
97
95
|
accounts: activeAccounts.length,
|
|
98
96
|
balance: hasBalanceData ? totalBalance : null,
|
|
99
97
|
pnl: hasPnlData ? totalPnl : null,
|
|
100
98
|
pnlPercent: null
|
|
101
|
-
};
|
|
99
|
+
});
|
|
102
100
|
} catch (e) {
|
|
103
101
|
// Ignore errors
|
|
104
102
|
}
|
|
105
103
|
} else {
|
|
106
|
-
|
|
104
|
+
clearCachedStats();
|
|
107
105
|
}
|
|
108
106
|
};
|
|
109
107
|
|
|
@@ -189,47 +187,6 @@ const banner = async () => {
|
|
|
189
187
|
const tagline = isMobile ? `HQX v${version}` : `Prop Futures Algo Trading v${version}`;
|
|
190
188
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
191
189
|
|
|
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
190
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
234
191
|
console.log();
|
|
235
192
|
};
|
|
@@ -393,7 +350,7 @@ const run = async () => {
|
|
|
393
350
|
const connCount = connections.count();
|
|
394
351
|
connections.disconnectAll();
|
|
395
352
|
currentService = null;
|
|
396
|
-
|
|
353
|
+
clearCachedStats();
|
|
397
354
|
console.log(chalk.yellow(`Disconnected ${connCount} connection${connCount > 1 ? 's' : ''}`));
|
|
398
355
|
break;
|
|
399
356
|
case 'exit':
|
|
@@ -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/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('../services/stats-cache');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Dashboard menu after login
|
|
@@ -44,10 +45,47 @@ const dashboardMenu = async (service) => {
|
|
|
44
45
|
const allConns = connections.getAll();
|
|
45
46
|
if (allConns.length > 0) {
|
|
46
47
|
const propfirms = allConns.slice(0, 3).map(c => c.propfirm || c.type || 'Connected');
|
|
47
|
-
const propfirmText = propfirms.map(p => chalk.green('
|
|
48
|
+
const propfirmText = propfirms.map(p => chalk.green('> ') + chalk.white(p)).join(' ');
|
|
48
49
|
console.log(makeLine(propfirmText, 'center'));
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// Show stats bar (Connections, Accounts, Balance, P&L)
|
|
53
|
+
const statsInfo = getCachedStats();
|
|
54
|
+
|
|
55
|
+
if (statsInfo) {
|
|
56
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
57
|
+
|
|
58
|
+
const connStr = `Connections: ${statsInfo.connections}`;
|
|
59
|
+
const accStr = `Accounts: ${statsInfo.accounts}`;
|
|
60
|
+
|
|
61
|
+
const balStr = statsInfo.balance !== null
|
|
62
|
+
? `$${statsInfo.balance.toLocaleString()}`
|
|
63
|
+
: '--';
|
|
64
|
+
const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
|
|
65
|
+
|
|
66
|
+
let pnlDisplay, pnlColor;
|
|
67
|
+
if (statsInfo.pnl !== null) {
|
|
68
|
+
const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
|
|
69
|
+
pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
70
|
+
pnlDisplay = `${pnlSign}$${Math.abs(statsInfo.pnl).toLocaleString()}`;
|
|
71
|
+
} else {
|
|
72
|
+
pnlColor = chalk.gray;
|
|
73
|
+
pnlDisplay = '--';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const statsText = connStr + ' ' + accStr + ' Balance: ' + balStr + ' P&L: ' + pnlDisplay;
|
|
77
|
+
const statsPlain = `${connStr} ${accStr} Balance: ${balStr} P&L: ${pnlDisplay}`;
|
|
78
|
+
const statsLeftPad = Math.floor((W - statsPlain.length) / 2);
|
|
79
|
+
const statsRightPad = W - statsPlain.length - statsLeftPad;
|
|
80
|
+
|
|
81
|
+
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
82
|
+
chalk.white(connStr) + ' ' +
|
|
83
|
+
chalk.white(accStr) + ' ' +
|
|
84
|
+
chalk.white('Balance: ') + balColor(balStr) + ' ' +
|
|
85
|
+
chalk.white('P&L: ') + pnlColor(pnlDisplay) +
|
|
86
|
+
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║'));
|
|
87
|
+
}
|
|
88
|
+
|
|
51
89
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
52
90
|
|
|
53
91
|
// 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 {
|
|
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,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
|
-
|
|
451
|
-
|
|
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
|
-
|
|
214
|
+
const { getContractsWithMonthCode } = require('../../config/contracts');
|
|
215
|
+
return { success: true, contracts: getContractsWithMonthCode() };
|
|
272
216
|
}
|
|
273
217
|
|
|
274
218
|
async searchContracts(searchText) {
|
|
275
|
-
const
|
|
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));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats Cache - Shared stats storage to avoid circular dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
let cachedStats = null;
|
|
6
|
+
|
|
7
|
+
const setCachedStats = (stats) => {
|
|
8
|
+
cachedStats = stats;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const getCachedStats = () => {
|
|
12
|
+
return cachedStats;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const clearCachedStats = () => {
|
|
16
|
+
cachedStats = null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
setCachedStats,
|
|
21
|
+
getCachedStats,
|
|
22
|
+
clearCachedStats
|
|
23
|
+
};
|