hedgequantx 1.5.3 → 1.5.5
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/algo/copy-trading.js +33 -38
- package/src/services/projectx/index.js +29 -0
- package/src/services/rithmic/index.js +25 -8
- package/src/utils/logger.js +16 -16
package/package.json
CHANGED
|
@@ -205,50 +205,45 @@ const copyTradingMenu = async () => {
|
|
|
205
205
|
const selectSymbol = async (service, label) => {
|
|
206
206
|
log.debug('selectSymbol called', { label, hasGetContracts: typeof service.getContracts === 'function' });
|
|
207
207
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const choices = contracts.map(c => ({ name: c.name || c.symbol, value: c }));
|
|
217
|
-
choices.push({ name: chalk.yellow('< Cancel'), value: null });
|
|
218
|
-
|
|
219
|
-
const { symbol } = await inquirer.prompt([{
|
|
220
|
-
type: 'list',
|
|
221
|
-
name: 'symbol',
|
|
222
|
-
message: `${label} Symbol:`,
|
|
223
|
-
choices,
|
|
224
|
-
pageSize: 15
|
|
225
|
-
}]);
|
|
226
|
-
return symbol;
|
|
227
|
-
}
|
|
208
|
+
let contracts = [];
|
|
209
|
+
|
|
210
|
+
// Try getContracts first
|
|
211
|
+
if (typeof service.getContracts === 'function') {
|
|
212
|
+
const result = await service.getContracts();
|
|
213
|
+
log.debug('getContracts result', { success: result?.success, count: result?.contracts?.length });
|
|
214
|
+
if (result.success && result.contracts?.length > 0) {
|
|
215
|
+
contracts = result.contracts;
|
|
228
216
|
}
|
|
229
|
-
log.error('No contract fetching method available');
|
|
230
|
-
return null;
|
|
231
217
|
}
|
|
232
218
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
|
|
219
|
+
// Fallback to searchContracts if no contracts yet
|
|
220
|
+
if (contracts.length === 0 && typeof service.searchContracts === 'function') {
|
|
221
|
+
log.debug('Trying searchContracts fallback');
|
|
222
|
+
// For Rithmic, searchContracts returns array directly
|
|
223
|
+
const searchResult = await service.searchContracts('ES');
|
|
224
|
+
log.debug('searchContracts result', { result: searchResult });
|
|
225
|
+
|
|
226
|
+
if (Array.isArray(searchResult)) {
|
|
227
|
+
contracts = searchResult;
|
|
228
|
+
} else if (searchResult?.contracts) {
|
|
229
|
+
contracts = searchResult.contracts;
|
|
230
|
+
}
|
|
244
231
|
}
|
|
245
232
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
233
|
+
// If still no contracts, show error
|
|
234
|
+
if (!contracts || contracts.length === 0) {
|
|
235
|
+
log.error('No contracts available');
|
|
236
|
+
console.log(chalk.red(' No contracts available for this service'));
|
|
237
|
+
return null;
|
|
251
238
|
}
|
|
239
|
+
|
|
240
|
+
log.debug('Contracts loaded', { count: contracts.length });
|
|
241
|
+
|
|
242
|
+
// Build choices - simple list without categories
|
|
243
|
+
const choices = contracts.map(c => ({
|
|
244
|
+
name: c.name || c.symbol,
|
|
245
|
+
value: c
|
|
246
|
+
}));
|
|
252
247
|
choices.push(new inquirer.Separator());
|
|
253
248
|
choices.push({ name: chalk.yellow('< Cancel'), value: null });
|
|
254
249
|
|
|
@@ -443,6 +443,35 @@ class ProjectXService {
|
|
|
443
443
|
|
|
444
444
|
// ==================== CONTRACTS ====================
|
|
445
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Get popular contracts for trading
|
|
448
|
+
*/
|
|
449
|
+
async getContracts() {
|
|
450
|
+
try {
|
|
451
|
+
// Search for popular futures symbols
|
|
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
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
446
475
|
async searchContracts(searchText) {
|
|
447
476
|
try {
|
|
448
477
|
const response = await this._request(
|
|
@@ -210,15 +210,32 @@ class RithmicService extends EventEmitter {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{ symbol: '
|
|
217
|
-
{ symbol: '
|
|
218
|
-
{ symbol: '
|
|
219
|
-
{ symbol: '
|
|
220
|
-
{ symbol: '
|
|
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' },
|
|
221
229
|
];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async getContracts() {
|
|
233
|
+
return { success: true, contracts: this._getAvailableContracts() };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async searchContracts(searchText) {
|
|
237
|
+
const contracts = this._getAvailableContracts();
|
|
238
|
+
if (!searchText) return contracts;
|
|
222
239
|
const search = searchText.toUpperCase();
|
|
223
240
|
return contracts.filter(c => c.symbol.includes(search) || c.name.toUpperCase().includes(search));
|
|
224
241
|
}
|
package/src/utils/logger.js
CHANGED
|
@@ -31,21 +31,19 @@ const COLORS = {
|
|
|
31
31
|
|
|
32
32
|
class Logger {
|
|
33
33
|
constructor() {
|
|
34
|
-
this.
|
|
34
|
+
this.consoleEnabled = process.env.HQX_DEBUG === '1';
|
|
35
35
|
this.level = LEVELS.DEBUG;
|
|
36
36
|
this.logFile = path.join(os.homedir(), '.hedgequantx', 'debug.log');
|
|
37
37
|
|
|
38
|
-
// Always write to file
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
-
}
|
|
44
|
-
// Clear log file on start
|
|
45
|
-
fs.writeFileSync(this.logFile, `=== HQX Debug Log Started ${new Date().toISOString()} ===\n`);
|
|
46
|
-
fs.appendFileSync(this.logFile, `Platform: ${process.platform}, Node: ${process.version}\n`);
|
|
47
|
-
fs.appendFileSync(this.logFile, `CWD: ${process.cwd()}\n\n`);
|
|
38
|
+
// Always write to file (logs are always saved)
|
|
39
|
+
const dir = path.dirname(this.logFile);
|
|
40
|
+
if (!fs.existsSync(dir)) {
|
|
41
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
48
42
|
}
|
|
43
|
+
// Clear log file on start
|
|
44
|
+
fs.writeFileSync(this.logFile, `=== HQX Log Started ${new Date().toISOString()} ===\n`);
|
|
45
|
+
fs.appendFileSync(this.logFile, `Platform: ${process.platform}, Node: ${process.version}\n`);
|
|
46
|
+
fs.appendFileSync(this.logFile, `CWD: ${process.cwd()}\n\n`);
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
_format(level, module, message, data) {
|
|
@@ -55,20 +53,22 @@ class Logger {
|
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
_log(level, levelName, module, message, data) {
|
|
58
|
-
if (
|
|
56
|
+
if (level > this.level) return;
|
|
59
57
|
|
|
60
58
|
const formatted = this._format(levelName, module, message, data);
|
|
61
|
-
const color = COLORS[levelName] || COLORS.RESET;
|
|
62
59
|
|
|
63
|
-
// Always write to file
|
|
60
|
+
// Always write to file (survives crashes)
|
|
64
61
|
try {
|
|
65
62
|
fs.appendFileSync(this.logFile, formatted + '\n');
|
|
66
63
|
} catch (e) {
|
|
67
64
|
// Ignore file write errors
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
// Console output
|
|
71
|
-
|
|
67
|
+
// Console output only if HQX_DEBUG=1
|
|
68
|
+
if (this.consoleEnabled) {
|
|
69
|
+
const color = COLORS[levelName] || COLORS.RESET;
|
|
70
|
+
console.error(`${color}${formatted}${COLORS.RESET}`);
|
|
71
|
+
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
error(module, message, data) {
|