hedgequantx 1.5.2 → 1.5.4
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/utils/logger.js +20 -21
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(
|
package/src/utils/logger.js
CHANGED
|
@@ -31,20 +31,19 @@ const COLORS = {
|
|
|
31
31
|
|
|
32
32
|
class Logger {
|
|
33
33
|
constructor() {
|
|
34
|
-
this.
|
|
35
|
-
this.fileEnabled = process.env.HQX_LOG_FILE === '1';
|
|
34
|
+
this.consoleEnabled = process.env.HQX_DEBUG === '1';
|
|
36
35
|
this.level = LEVELS.DEBUG;
|
|
37
36
|
this.logFile = path.join(os.homedir(), '.hedgequantx', 'debug.log');
|
|
38
37
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
-
}
|
|
45
|
-
// Clear log file on start
|
|
46
|
-
fs.writeFileSync(this.logFile, `=== HQX Debug Log Started ${new Date().toISOString()} ===\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 });
|
|
47
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`);
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
_format(level, module, message, data) {
|
|
@@ -54,21 +53,21 @@ class Logger {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
_log(level, levelName, module, message, data) {
|
|
57
|
-
if (
|
|
56
|
+
if (level > this.level) return;
|
|
58
57
|
|
|
59
58
|
const formatted = this._format(levelName, module, message, data);
|
|
60
|
-
const color = COLORS[levelName] || COLORS.RESET;
|
|
61
59
|
|
|
62
|
-
//
|
|
63
|
-
|
|
60
|
+
// Always write to file (survives crashes)
|
|
61
|
+
try {
|
|
62
|
+
fs.appendFileSync(this.logFile, formatted + '\n');
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Ignore file write errors
|
|
65
|
+
}
|
|
64
66
|
|
|
65
|
-
//
|
|
66
|
-
if (this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} catch (e) {
|
|
70
|
-
// Ignore file write errors
|
|
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}`);
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|