hedgequantx 1.8.34 → 1.8.36
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/pages/accounts.js +2 -2
- package/src/pages/algo/copy-trading.js +35 -20
- package/src/pages/algo/one-account.js +29 -25
- package/src/pages/orders.js +1 -1
- package/src/pages/positions.js +1 -1
- package/src/services/rithmic/accounts.js +2 -2
- package/src/services/rithmic/constants.js +152 -0
- package/src/services/rithmic/index.js +18 -3
- package/src/utils/prompts.js +12 -5
package/package.json
CHANGED
package/src/pages/accounts.js
CHANGED
|
@@ -60,8 +60,8 @@ const showAccounts = async (service) => {
|
|
|
60
60
|
const acc1 = allAccounts[i];
|
|
61
61
|
const acc2 = allAccounts[i + 1];
|
|
62
62
|
|
|
63
|
-
const name1 = acc1.
|
|
64
|
-
const name2 = acc2 ? (acc2.
|
|
63
|
+
const name1 = acc1.accountId || `Account #${acc1.accountId}`;
|
|
64
|
+
const name2 = acc2 ? (acc2.accountId || `Account #${acc2.accountId}`) : '';
|
|
65
65
|
|
|
66
66
|
draw2ColHeader(name1.substring(0, col1 - 4), name2.substring(0, col2 - 4), boxWidth);
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ const copyTradingMenu = async () => {
|
|
|
72
72
|
// Step 1: Select Lead Account
|
|
73
73
|
console.log(chalk.cyan(' Step 1: Select LEAD Account'));
|
|
74
74
|
const leadOptions = allAccounts.map((a, i) => ({
|
|
75
|
-
label: `${a.propfirm} - ${a.account.
|
|
75
|
+
label: `${a.propfirm} - ${a.account.accountId}${a.account.balance !== null ? ` ($${a.account.balance.toLocaleString()})` : ''}`,
|
|
76
76
|
value: i
|
|
77
77
|
}));
|
|
78
78
|
leadOptions.push({ label: '< Cancel', value: -1 });
|
|
@@ -88,7 +88,7 @@ const copyTradingMenu = async () => {
|
|
|
88
88
|
.map((a, i) => ({ a, i }))
|
|
89
89
|
.filter(x => x.i !== leadIdx)
|
|
90
90
|
.map(x => ({
|
|
91
|
-
label: `${x.a.propfirm} - ${x.a.account.
|
|
91
|
+
label: `${x.a.propfirm} - ${x.a.account.accountId}${x.a.account.balance !== null ? ` ($${x.a.account.balance.toLocaleString()})` : ''}`,
|
|
92
92
|
value: x.i
|
|
93
93
|
}));
|
|
94
94
|
followerOptions.push({ label: '< Cancel', value: -1 });
|
|
@@ -180,7 +180,7 @@ const getContractsFromAPI = async () => {
|
|
|
180
180
|
};
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
|
-
* Symbol selection helper -
|
|
183
|
+
* Symbol selection helper - grouped by category with indices first
|
|
184
184
|
*/
|
|
185
185
|
const selectSymbol = async (service, label) => {
|
|
186
186
|
const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
|
|
@@ -193,6 +193,7 @@ const selectSymbol = async (service, label) => {
|
|
|
193
193
|
if (!contracts && typeof service.getContracts === 'function') {
|
|
194
194
|
const result = await service.getContracts();
|
|
195
195
|
if (result.success && result.contracts?.length > 0) {
|
|
196
|
+
// Contracts from Rithmic are already categorized and sorted
|
|
196
197
|
contracts = result.contracts;
|
|
197
198
|
}
|
|
198
199
|
}
|
|
@@ -203,23 +204,36 @@ const selectSymbol = async (service, label) => {
|
|
|
203
204
|
return null;
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
// Sort: Popular indices first (ES, NQ, YM, RTY, MES, MNQ)
|
|
207
|
-
const prioritySymbols = ['ES', 'NQ', 'YM', 'RTY', 'MES', 'MNQ', 'MYM', 'M2K', 'NKD', 'CL', 'GC'];
|
|
208
|
-
contracts.sort((a, b) => {
|
|
209
|
-
const aSymbol = (a.symbol || a.name || '').toUpperCase();
|
|
210
|
-
const bSymbol = (b.symbol || b.name || '').toUpperCase();
|
|
211
|
-
const aIdx = prioritySymbols.findIndex(p => aSymbol.startsWith(p));
|
|
212
|
-
const bIdx = prioritySymbols.findIndex(p => bSymbol.startsWith(p));
|
|
213
|
-
if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx;
|
|
214
|
-
if (aIdx !== -1) return -1;
|
|
215
|
-
if (bIdx !== -1) return 1;
|
|
216
|
-
return (a.name || '').localeCompare(b.name || '');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
207
|
spinner.succeed(`Found ${contracts.length} contracts`);
|
|
220
208
|
|
|
221
|
-
|
|
222
|
-
options
|
|
209
|
+
// Build options with category headers (if contracts have category info)
|
|
210
|
+
const options = [];
|
|
211
|
+
let currentCategory = null;
|
|
212
|
+
|
|
213
|
+
for (const c of contracts) {
|
|
214
|
+
// Add category header when category changes (if available)
|
|
215
|
+
if (c.categoryName && c.categoryName !== currentCategory) {
|
|
216
|
+
currentCategory = c.categoryName;
|
|
217
|
+
options.push({
|
|
218
|
+
label: chalk.cyan.bold(`── ${currentCategory} ──`),
|
|
219
|
+
value: null,
|
|
220
|
+
disabled: true
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Format label based on available data
|
|
225
|
+
const symbolDisplay = c.symbol || c.name;
|
|
226
|
+
const nameDisplay = c.name || c.symbol;
|
|
227
|
+
const exchangeDisplay = c.exchange ? ` (${c.exchange})` : '';
|
|
228
|
+
const label = c.categoryName
|
|
229
|
+
? ` ${symbolDisplay} - ${nameDisplay}${exchangeDisplay}`
|
|
230
|
+
: `${nameDisplay}${exchangeDisplay}`;
|
|
231
|
+
|
|
232
|
+
options.push({ label, value: c });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
options.push({ label: '', value: null, disabled: true }); // Spacer
|
|
236
|
+
options.push({ label: chalk.gray('< Cancel'), value: null });
|
|
223
237
|
|
|
224
238
|
return await prompts.selectOption(`${label} Symbol:`, options);
|
|
225
239
|
} catch (e) {
|
|
@@ -235,8 +249,9 @@ const selectSymbol = async (service, label) => {
|
|
|
235
249
|
const launchCopyTrading = async (config) => {
|
|
236
250
|
const { lead, follower, dailyTarget, maxRisk, showNames } = config;
|
|
237
251
|
|
|
238
|
-
|
|
239
|
-
const
|
|
252
|
+
// NEVER show real name - only account ID or masked
|
|
253
|
+
const leadName = showNames ? lead.account.accountId : 'HQX Lead *****';
|
|
254
|
+
const followerName = showNames ? follower.account.accountId : 'HQX Follower *****';
|
|
240
255
|
|
|
241
256
|
const ui = new AlgoUI({ subtitle: 'HQX Copy Trading', mode: 'copy-trading' });
|
|
242
257
|
|
|
@@ -49,7 +49,7 @@ const oneAccountMenu = async (service) => {
|
|
|
49
49
|
|
|
50
50
|
// Select account
|
|
51
51
|
const options = activeAccounts.map(acc => ({
|
|
52
|
-
label: `${acc.
|
|
52
|
+
label: `${acc.accountId} (${acc.propfirm || 'Unknown'})${acc.balance !== null ? ` - $${acc.balance.toLocaleString()}` : ''}`,
|
|
53
53
|
value: acc
|
|
54
54
|
}));
|
|
55
55
|
options.push({ label: '< Back', value: 'back' });
|
|
@@ -72,7 +72,7 @@ const oneAccountMenu = async (service) => {
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Symbol selection -
|
|
75
|
+
* Symbol selection - grouped by category with indices first
|
|
76
76
|
*/
|
|
77
77
|
const selectSymbol = async (service, account) => {
|
|
78
78
|
const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
|
|
@@ -83,33 +83,36 @@ const selectSymbol = async (service, account) => {
|
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
//
|
|
87
|
-
const contracts = contractsResult.contracts
|
|
88
|
-
...c,
|
|
89
|
-
symbol: c.name || c.symbol,
|
|
90
|
-
name: c.description || c.name || c.symbol
|
|
91
|
-
}));
|
|
92
|
-
|
|
93
|
-
// Sort: Popular indices first (ES, NQ, YM, RTY, MES, MNQ)
|
|
94
|
-
const prioritySymbols = ['ES', 'NQ', 'YM', 'RTY', 'MES', 'MNQ', 'MYM', 'M2K', 'NKD', 'CL', 'GC'];
|
|
95
|
-
contracts.sort((a, b) => {
|
|
96
|
-
const aSymbol = (a.symbol || '').toUpperCase();
|
|
97
|
-
const bSymbol = (b.symbol || '').toUpperCase();
|
|
98
|
-
const aIdx = prioritySymbols.findIndex(p => aSymbol.startsWith(p));
|
|
99
|
-
const bIdx = prioritySymbols.findIndex(p => bSymbol.startsWith(p));
|
|
100
|
-
if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx;
|
|
101
|
-
if (aIdx !== -1) return -1;
|
|
102
|
-
if (bIdx !== -1) return 1;
|
|
103
|
-
return (a.name || '').localeCompare(b.name || '');
|
|
104
|
-
});
|
|
86
|
+
// Contracts are already sorted by category from getContracts()
|
|
87
|
+
const contracts = contractsResult.contracts;
|
|
105
88
|
|
|
106
89
|
spinner.succeed(`Found ${contracts.length} contracts`);
|
|
107
90
|
|
|
108
|
-
|
|
109
|
-
options
|
|
91
|
+
// Build options with category headers
|
|
92
|
+
const options = [];
|
|
93
|
+
let currentCategory = null;
|
|
94
|
+
|
|
95
|
+
for (const c of contracts) {
|
|
96
|
+
// Add category header when category changes
|
|
97
|
+
if (c.categoryName !== currentCategory) {
|
|
98
|
+
currentCategory = c.categoryName;
|
|
99
|
+
options.push({
|
|
100
|
+
label: chalk.cyan.bold(`── ${currentCategory} ──`),
|
|
101
|
+
value: null,
|
|
102
|
+
disabled: true
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Format: "ESH6 - E-mini S&P 500 (CME)"
|
|
107
|
+
const label = ` ${c.symbol} - ${c.name} (${c.exchange})`;
|
|
108
|
+
options.push({ label, value: c });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
options.push({ label: '', value: null, disabled: true }); // Spacer
|
|
112
|
+
options.push({ label: chalk.gray('< Back'), value: 'back' });
|
|
110
113
|
|
|
111
114
|
const contract = await prompts.selectOption(chalk.yellow('Select Symbol:'), options);
|
|
112
|
-
return contract === 'back' ? null : contract;
|
|
115
|
+
return contract === 'back' || contract === null ? null : contract;
|
|
113
116
|
};
|
|
114
117
|
|
|
115
118
|
/**
|
|
@@ -143,7 +146,8 @@ const configureAlgo = async (account, contract) => {
|
|
|
143
146
|
*/
|
|
144
147
|
const launchAlgo = async (service, account, contract, config) => {
|
|
145
148
|
const { contracts, dailyTarget, maxRisk, showName } = config;
|
|
146
|
-
|
|
149
|
+
// NEVER show real name - only account ID or masked
|
|
150
|
+
const accountName = showName ? account.accountId : 'HQX *****';
|
|
147
151
|
const symbolName = contract.name || contract.symbol;
|
|
148
152
|
|
|
149
153
|
const ui = new AlgoUI({ subtitle: 'HQX Ultra-Scalping', mode: 'one-account' });
|
package/src/pages/orders.js
CHANGED
|
@@ -44,7 +44,7 @@ const showOrders = async (service) => {
|
|
|
44
44
|
const result = await account.service.getOrders(account.accountId);
|
|
45
45
|
if (result.success && result.orders?.length > 0) {
|
|
46
46
|
result.orders.forEach(order => {
|
|
47
|
-
allOrders.push({ ...order, accountName: account.
|
|
47
|
+
allOrders.push({ ...order, accountName: account.accountId, propfirm: account.propfirm });
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
} catch (e) {}
|
package/src/pages/positions.js
CHANGED
|
@@ -44,7 +44,7 @@ const showPositions = async (service) => {
|
|
|
44
44
|
const result = await account.service.getPositions(account.accountId);
|
|
45
45
|
if (result.success && result.positions?.length > 0) {
|
|
46
46
|
result.positions.forEach(pos => {
|
|
47
|
-
allPositions.push({ ...pos, accountName: account.
|
|
47
|
+
allPositions.push({ ...pos, accountName: account.accountId, propfirm: account.propfirm });
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
} catch (e) {}
|
|
@@ -118,8 +118,8 @@ const getTradingAccounts = async (service) => {
|
|
|
118
118
|
return {
|
|
119
119
|
accountId: hashAccountId(acc.accountId),
|
|
120
120
|
rithmicAccountId: acc.accountId,
|
|
121
|
-
accountName: acc.
|
|
122
|
-
name: acc.
|
|
121
|
+
accountName: acc.accountId, // Never expose real name - only account ID
|
|
122
|
+
name: acc.accountId, // Never expose real name - only account ID
|
|
123
123
|
balance: accountBalance,
|
|
124
124
|
profitAndLoss: dayPnL !== null ? dayPnL : (openPnL !== null || closedPnL !== null ? (openPnL || 0) + (closedPnL || 0) : null),
|
|
125
125
|
openPnL: openPnL,
|
|
@@ -165,6 +165,155 @@ const PROTO_FILES = [
|
|
|
165
165
|
'response_front_month_contract.proto',
|
|
166
166
|
];
|
|
167
167
|
|
|
168
|
+
// Symbol Categories - Order matters for display (indices first)
|
|
169
|
+
const SYMBOL_CATEGORIES = {
|
|
170
|
+
INDICES: {
|
|
171
|
+
name: 'Indices',
|
|
172
|
+
order: 1,
|
|
173
|
+
symbols: {
|
|
174
|
+
// E-mini Indices
|
|
175
|
+
'ES': { name: 'E-mini S&P 500', tickSize: 0.25, tickValue: 12.50 },
|
|
176
|
+
'NQ': { name: 'E-mini Nasdaq-100', tickSize: 0.25, tickValue: 5.00 },
|
|
177
|
+
'YM': { name: 'E-mini Dow Jones', tickSize: 1.00, tickValue: 5.00 },
|
|
178
|
+
'RTY': { name: 'E-mini Russell 2000', tickSize: 0.10, tickValue: 5.00 },
|
|
179
|
+
'EMD': { name: 'E-mini S&P MidCap 400', tickSize: 0.10, tickValue: 10.00 },
|
|
180
|
+
// Micro Indices
|
|
181
|
+
'MES': { name: 'Micro E-mini S&P 500', tickSize: 0.25, tickValue: 1.25 },
|
|
182
|
+
'MNQ': { name: 'Micro E-mini Nasdaq-100', tickSize: 0.25, tickValue: 0.50 },
|
|
183
|
+
'MYM': { name: 'Micro E-mini Dow Jones', tickSize: 1.00, tickValue: 0.50 },
|
|
184
|
+
'M2K': { name: 'Micro E-mini Russell 2000', tickSize: 0.10, tickValue: 0.50 },
|
|
185
|
+
// International Indices
|
|
186
|
+
'NKD': { name: 'Nikkei 225 (USD)', tickSize: 5.00, tickValue: 25.00 },
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
ENERGY: {
|
|
190
|
+
name: 'Energy',
|
|
191
|
+
order: 2,
|
|
192
|
+
symbols: {
|
|
193
|
+
'CL': { name: 'Crude Oil WTI', tickSize: 0.01, tickValue: 10.00 },
|
|
194
|
+
'MCL': { name: 'Micro Crude Oil WTI', tickSize: 0.01, tickValue: 1.00 },
|
|
195
|
+
'BZ': { name: 'Brent Crude Oil', tickSize: 0.01, tickValue: 10.00 },
|
|
196
|
+
'NG': { name: 'Natural Gas', tickSize: 0.001, tickValue: 10.00 },
|
|
197
|
+
'HO': { name: 'Heating Oil', tickSize: 0.0001, tickValue: 4.20 },
|
|
198
|
+
'RB': { name: 'RBOB Gasoline', tickSize: 0.0001, tickValue: 4.20 },
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
METALS: {
|
|
202
|
+
name: 'Metals',
|
|
203
|
+
order: 3,
|
|
204
|
+
symbols: {
|
|
205
|
+
'GC': { name: 'Gold', tickSize: 0.10, tickValue: 10.00 },
|
|
206
|
+
'MGC': { name: 'Micro Gold', tickSize: 0.10, tickValue: 1.00 },
|
|
207
|
+
'1OZ': { name: '1 Ounce Gold', tickSize: 0.25, tickValue: 0.25 },
|
|
208
|
+
'SI': { name: 'Silver', tickSize: 0.005, tickValue: 25.00 },
|
|
209
|
+
'SIL': { name: 'Silver 1000oz', tickSize: 0.005, tickValue: 5.00 },
|
|
210
|
+
'HG': { name: 'Copper', tickSize: 0.0005, tickValue: 12.50 },
|
|
211
|
+
'MHG': { name: 'Micro Copper', tickSize: 0.0005, tickValue: 1.25 },
|
|
212
|
+
'PL': { name: 'Platinum', tickSize: 0.10, tickValue: 5.00 },
|
|
213
|
+
'PA': { name: 'Palladium', tickSize: 0.10, tickValue: 10.00 },
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
CURRENCIES: {
|
|
217
|
+
name: 'Currencies (FX)',
|
|
218
|
+
order: 4,
|
|
219
|
+
symbols: {
|
|
220
|
+
'6E': { name: 'Euro FX', tickSize: 0.00005, tickValue: 6.25 },
|
|
221
|
+
'M6E': { name: 'Micro Euro FX', tickSize: 0.0001, tickValue: 1.25 },
|
|
222
|
+
'6B': { name: 'British Pound', tickSize: 0.0001, tickValue: 6.25 },
|
|
223
|
+
'M6B': { name: 'Micro British Pound', tickSize: 0.0001, tickValue: 0.625 },
|
|
224
|
+
'6J': { name: 'Japanese Yen', tickSize: 0.0000005, tickValue: 6.25 },
|
|
225
|
+
'6A': { name: 'Australian Dollar', tickSize: 0.0001, tickValue: 10.00 },
|
|
226
|
+
'M6A': { name: 'Micro Australian Dollar', tickSize: 0.0001, tickValue: 1.00 },
|
|
227
|
+
'6C': { name: 'Canadian Dollar', tickSize: 0.00005, tickValue: 5.00 },
|
|
228
|
+
'6S': { name: 'Swiss Franc', tickSize: 0.0001, tickValue: 12.50 },
|
|
229
|
+
'6N': { name: 'New Zealand Dollar', tickSize: 0.0001, tickValue: 10.00 },
|
|
230
|
+
'6M': { name: 'Mexican Peso', tickSize: 0.00001, tickValue: 5.00 },
|
|
231
|
+
'E7': { name: 'E-mini Euro FX', tickSize: 0.0001, tickValue: 6.25 },
|
|
232
|
+
'RF': { name: 'Euro FX/Swiss Franc', tickSize: 0.0001, tickValue: 12.50 },
|
|
233
|
+
'RP': { name: 'Euro FX/British Pound', tickSize: 0.00005, tickValue: 6.25 },
|
|
234
|
+
'RY': { name: 'Euro FX/Japanese Yen', tickSize: 0.01, tickValue: 6.25 },
|
|
235
|
+
'SEK': { name: 'Swedish Krona', tickSize: 0.00001, tickValue: 12.50 },
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
CRYPTO: {
|
|
239
|
+
name: 'Crypto',
|
|
240
|
+
order: 5,
|
|
241
|
+
symbols: {
|
|
242
|
+
'BTC': { name: 'Bitcoin', tickSize: 5.00, tickValue: 25.00 },
|
|
243
|
+
'MBT': { name: 'Micro Bitcoin', tickSize: 5.00, tickValue: 0.50 },
|
|
244
|
+
'ETH': { name: 'Ether', tickSize: 0.25, tickValue: 12.50 },
|
|
245
|
+
'MET': { name: 'Micro Ether', tickSize: 0.05, tickValue: 0.25 },
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
RATES: {
|
|
249
|
+
name: 'Interest Rates',
|
|
250
|
+
order: 6,
|
|
251
|
+
symbols: {
|
|
252
|
+
'ZB': { name: '30-Year T-Bond', tickSize: 0.03125, tickValue: 31.25 },
|
|
253
|
+
'ZN': { name: '10-Year T-Note', tickSize: 0.015625, tickValue: 15.625 },
|
|
254
|
+
'ZF': { name: '5-Year T-Note', tickSize: 0.0078125, tickValue: 7.8125 },
|
|
255
|
+
'ZT': { name: '2-Year T-Note', tickSize: 0.0078125, tickValue: 15.625 },
|
|
256
|
+
'TN': { name: 'Ultra 10-Year T-Note', tickSize: 0.015625, tickValue: 15.625 },
|
|
257
|
+
'ZQ': { name: '30-Day Fed Funds', tickSize: 0.0025, tickValue: 10.4167 },
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
AGRICULTURE: {
|
|
261
|
+
name: 'Agriculture',
|
|
262
|
+
order: 7,
|
|
263
|
+
symbols: {
|
|
264
|
+
'ZC': { name: 'Corn', tickSize: 0.25, tickValue: 12.50 },
|
|
265
|
+
'ZS': { name: 'Soybeans', tickSize: 0.25, tickValue: 12.50 },
|
|
266
|
+
'ZW': { name: 'Wheat', tickSize: 0.25, tickValue: 12.50 },
|
|
267
|
+
'ZL': { name: 'Soybean Oil', tickSize: 0.01, tickValue: 6.00 },
|
|
268
|
+
'ZM': { name: 'Soybean Meal', tickSize: 0.10, tickValue: 10.00 },
|
|
269
|
+
'ZO': { name: 'Oats', tickSize: 0.25, tickValue: 12.50 },
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
MEATS: {
|
|
273
|
+
name: 'Meats',
|
|
274
|
+
order: 8,
|
|
275
|
+
symbols: {
|
|
276
|
+
'LE': { name: 'Live Cattle', tickSize: 0.025, tickValue: 10.00 },
|
|
277
|
+
'HE': { name: 'Lean Hogs', tickSize: 0.025, tickValue: 10.00 },
|
|
278
|
+
'GF': { name: 'Feeder Cattle', tickSize: 0.025, tickValue: 12.50 },
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get symbol info (category, name, tick info)
|
|
285
|
+
*/
|
|
286
|
+
function getSymbolInfo(baseSymbol) {
|
|
287
|
+
for (const [catKey, category] of Object.entries(SYMBOL_CATEGORIES)) {
|
|
288
|
+
if (category.symbols[baseSymbol]) {
|
|
289
|
+
return {
|
|
290
|
+
category: catKey,
|
|
291
|
+
categoryName: category.name,
|
|
292
|
+
categoryOrder: category.order,
|
|
293
|
+
...category.symbols[baseSymbol]
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Unknown symbol
|
|
298
|
+
return {
|
|
299
|
+
category: 'OTHER',
|
|
300
|
+
categoryName: 'Other',
|
|
301
|
+
categoryOrder: 99,
|
|
302
|
+
name: baseSymbol,
|
|
303
|
+
tickSize: null,
|
|
304
|
+
tickValue: null
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get all categories in display order
|
|
310
|
+
*/
|
|
311
|
+
function getCategoryOrder() {
|
|
312
|
+
return Object.entries(SYMBOL_CATEGORIES)
|
|
313
|
+
.sort((a, b) => a[1].order - b[1].order)
|
|
314
|
+
.map(([key, val]) => ({ key, name: val.name, order: val.order }));
|
|
315
|
+
}
|
|
316
|
+
|
|
168
317
|
module.exports = {
|
|
169
318
|
RITHMIC_ENDPOINTS,
|
|
170
319
|
RITHMIC_SYSTEMS,
|
|
@@ -173,4 +322,7 @@ module.exports = {
|
|
|
173
322
|
RES,
|
|
174
323
|
STREAM,
|
|
175
324
|
PROTO_FILES,
|
|
325
|
+
SYMBOL_CATEGORIES,
|
|
326
|
+
getSymbolInfo,
|
|
327
|
+
getCategoryOrder,
|
|
176
328
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const EventEmitter = require('events');
|
|
7
7
|
const { RithmicConnection } = require('./connection');
|
|
8
|
-
const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS } = require('./constants');
|
|
8
|
+
const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS, getSymbolInfo, getCategoryOrder } = require('./constants');
|
|
9
9
|
const { createOrderHandler, createPnLHandler } = require('./handlers');
|
|
10
10
|
const { fetchAccounts, getTradingAccounts, requestPnLSnapshot, subscribePnLUpdates, getPositions, hashAccountId } = require('./accounts');
|
|
11
11
|
const { placeOrder, cancelOrder, getOrders, getOrderHistory, closePosition } = require('./orders');
|
|
@@ -425,19 +425,34 @@ class RithmicService extends EventEmitter {
|
|
|
425
425
|
setTimeout(() => {
|
|
426
426
|
this.tickerConn.removeListener('message', frontMonthHandler);
|
|
427
427
|
|
|
428
|
-
// Merge with product names
|
|
428
|
+
// Merge with product names and add category info
|
|
429
429
|
const results = [];
|
|
430
430
|
for (const [baseSymbol, contract] of contracts) {
|
|
431
431
|
const productKey = `${baseSymbol}:${contract.exchange}`;
|
|
432
432
|
const product = productsToCheck.get(productKey);
|
|
433
|
+
const symbolInfo = getSymbolInfo(baseSymbol);
|
|
434
|
+
|
|
433
435
|
results.push({
|
|
434
436
|
symbol: contract.symbol,
|
|
435
437
|
baseSymbol: baseSymbol,
|
|
436
|
-
name:
|
|
438
|
+
name: symbolInfo.name || product?.productName || baseSymbol,
|
|
437
439
|
exchange: contract.exchange,
|
|
440
|
+
category: symbolInfo.category,
|
|
441
|
+
categoryName: symbolInfo.categoryName,
|
|
442
|
+
categoryOrder: symbolInfo.categoryOrder,
|
|
443
|
+
tickSize: symbolInfo.tickSize,
|
|
444
|
+
tickValue: symbolInfo.tickValue,
|
|
438
445
|
});
|
|
439
446
|
}
|
|
440
447
|
|
|
448
|
+
// Sort by category order, then by symbol name within category
|
|
449
|
+
results.sort((a, b) => {
|
|
450
|
+
if (a.categoryOrder !== b.categoryOrder) {
|
|
451
|
+
return a.categoryOrder - b.categoryOrder;
|
|
452
|
+
}
|
|
453
|
+
return a.baseSymbol.localeCompare(b.baseSymbol);
|
|
454
|
+
});
|
|
455
|
+
|
|
441
456
|
debug(`Got ${results.length} tradeable contracts from API`);
|
|
442
457
|
resolve(results);
|
|
443
458
|
}, 8000);
|
package/src/utils/prompts.js
CHANGED
|
@@ -126,6 +126,7 @@ const numberInput = async (message, defaultVal = 1, min = 1, max = 1000) => {
|
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
128
|
* Select - arrow keys navigation
|
|
129
|
+
* Supports disabled options (separators) via opt.disabled
|
|
129
130
|
*/
|
|
130
131
|
const selectOption = async (message, options) => {
|
|
131
132
|
// Close shared readline before inquirer to avoid conflicts
|
|
@@ -135,10 +136,16 @@ const selectOption = async (message, options) => {
|
|
|
135
136
|
}
|
|
136
137
|
prepareStdin();
|
|
137
138
|
|
|
138
|
-
const choices = options.map(opt =>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
const choices = options.map(opt => {
|
|
140
|
+
if (opt.disabled) {
|
|
141
|
+
// Use inquirer Separator for disabled items (category headers)
|
|
142
|
+
return new inquirer.Separator(opt.label);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
name: opt.label,
|
|
146
|
+
value: opt.value
|
|
147
|
+
};
|
|
148
|
+
});
|
|
142
149
|
|
|
143
150
|
const { value } = await inquirer.prompt([{
|
|
144
151
|
type: 'list',
|
|
@@ -147,7 +154,7 @@ const selectOption = async (message, options) => {
|
|
|
147
154
|
choices,
|
|
148
155
|
prefix: '',
|
|
149
156
|
loop: false,
|
|
150
|
-
pageSize:
|
|
157
|
+
pageSize: 20 // Increased to show more symbols
|
|
151
158
|
}]);
|
|
152
159
|
|
|
153
160
|
return value;
|