hedgequantx 2.6.139 → 2.6.141
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
|
@@ -948,8 +948,6 @@ const launchCopyTrading = async (config) => {
|
|
|
948
948
|
};
|
|
949
949
|
}
|
|
950
950
|
|
|
951
|
-
ui.cleanup();
|
|
952
|
-
|
|
953
951
|
// Duration
|
|
954
952
|
const durationMs = Date.now() - stats.startTime;
|
|
955
953
|
const hours = Math.floor(durationMs / 3600000);
|
|
@@ -961,6 +959,11 @@ const launchCopyTrading = async (config) => {
|
|
|
961
959
|
? `${minutes}m ${seconds}s`
|
|
962
960
|
: `${seconds}s`;
|
|
963
961
|
|
|
962
|
+
// Close log file with session summary
|
|
963
|
+
try { ui.closeLog(stats); } catch {}
|
|
964
|
+
|
|
965
|
+
ui.cleanup();
|
|
966
|
+
|
|
964
967
|
// Summary
|
|
965
968
|
renderSessionSummary(stats, stopReason);
|
|
966
969
|
await prompts.waitForEnter();
|
|
@@ -1368,6 +1368,9 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
1368
1368
|
// Cleanup keyboard handler
|
|
1369
1369
|
try { if (cleanupKeys) cleanupKeys(); } catch {}
|
|
1370
1370
|
|
|
1371
|
+
// Close log file with session summary
|
|
1372
|
+
try { ui.closeLog(stats); } catch {}
|
|
1373
|
+
|
|
1371
1374
|
// Cleanup UI
|
|
1372
1375
|
try { ui.cleanup(); } catch {}
|
|
1373
1376
|
|
|
@@ -1416,13 +1419,12 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
1416
1419
|
: 'HQX *****';
|
|
1417
1420
|
const rithmicAccountId = account.rithmicAccountId || account.accountId;
|
|
1418
1421
|
|
|
1419
|
-
// Build symbols string for UI (clean names without X1 suffix,
|
|
1422
|
+
// Build symbols string for UI (clean names without X1 suffix, symbol only)
|
|
1420
1423
|
const symbolsDisplay = contracts.map(c => {
|
|
1421
1424
|
let name = c.name || c.symbol;
|
|
1422
1425
|
// Remove X1, X2 suffix (Rithmic internal suffixes)
|
|
1423
1426
|
name = name.replace(/X\d+$/, '');
|
|
1424
|
-
|
|
1425
|
-
return qty > 1 ? `${name}x${qty}` : name;
|
|
1427
|
+
return name;
|
|
1426
1428
|
}).join(', ');
|
|
1427
1429
|
|
|
1428
1430
|
const ui = new AlgoUI({
|
|
@@ -2078,16 +2080,8 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
2078
2080
|
|
|
2079
2081
|
// Cleanup keyboard
|
|
2080
2082
|
try { if (cleanupKeys) cleanupKeys(); } catch {}
|
|
2081
|
-
try { ui.cleanup(); } catch {}
|
|
2082
|
-
|
|
2083
|
-
try {
|
|
2084
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
2085
|
-
process.stdin.resume();
|
|
2086
|
-
} catch {}
|
|
2087
2083
|
|
|
2088
|
-
//
|
|
2089
|
-
// SESSION SUMMARY
|
|
2090
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
2084
|
+
// Calculate duration before closeLog
|
|
2091
2085
|
const durationMs = Date.now() - stats.startTime;
|
|
2092
2086
|
const hours = Math.floor(durationMs / 3600000);
|
|
2093
2087
|
const minutes = Math.floor((durationMs % 3600000) / 60000);
|
|
@@ -2098,6 +2092,19 @@ const launchMultiSymbolRithmic = async (service, account, contracts, config) =>
|
|
|
2098
2092
|
? `${minutes}m ${seconds}s`
|
|
2099
2093
|
: `${seconds}s`;
|
|
2100
2094
|
|
|
2095
|
+
// Close log file with session summary
|
|
2096
|
+
try { ui.closeLog(stats); } catch {}
|
|
2097
|
+
|
|
2098
|
+
try { ui.cleanup(); } catch {}
|
|
2099
|
+
|
|
2100
|
+
try {
|
|
2101
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
2102
|
+
process.stdin.resume();
|
|
2103
|
+
} catch {}
|
|
2104
|
+
|
|
2105
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2106
|
+
// SESSION SUMMARY (duration already calculated above)
|
|
2107
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2101
2108
|
// Render multi-symbol summary with same style as single-symbol
|
|
2102
2109
|
renderMultiSymbolSummary(stats, stopReason, stats.symbolStats);
|
|
2103
2110
|
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const chalk = require('chalk');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
7
10
|
|
|
8
11
|
// Box drawing characters
|
|
9
12
|
const BOX = {
|
|
@@ -146,12 +149,54 @@ class AlgoUI {
|
|
|
146
149
|
this.lastStatsHash = ''; // Track stats changes
|
|
147
150
|
this.lastLogsHash = ''; // Track logs changes
|
|
148
151
|
this.lastSpinnerUpdate = 0; // Rate limit spinner updates
|
|
152
|
+
|
|
153
|
+
// Session log file
|
|
154
|
+
this.logFile = null;
|
|
155
|
+
this.logStream = null;
|
|
156
|
+
this._initLogFile();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_initLogFile() {
|
|
160
|
+
try {
|
|
161
|
+
// Create logs directory in user home
|
|
162
|
+
const logsDir = path.join(os.homedir(), '.hqx', 'logs');
|
|
163
|
+
if (!fs.existsSync(logsDir)) {
|
|
164
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Create session log file with timestamp
|
|
168
|
+
const now = new Date();
|
|
169
|
+
const dateStr = now.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
170
|
+
const timeStr = now.toTimeString().split(' ')[0].replace(/:/g, '-'); // HH-MM-SS
|
|
171
|
+
const mode = this.config.mode || 'algo';
|
|
172
|
+
this.logFile = path.join(logsDir, `session_${mode}_${dateStr}_${timeStr}.log`);
|
|
173
|
+
|
|
174
|
+
// Open write stream
|
|
175
|
+
this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
|
|
176
|
+
|
|
177
|
+
// Write session header
|
|
178
|
+
this.logStream.write(`\n${'='.repeat(80)}\n`);
|
|
179
|
+
this.logStream.write(`HQX ALGO SESSION LOG\n`);
|
|
180
|
+
this.logStream.write(`Mode: ${this.config.mode || 'unknown'}\n`);
|
|
181
|
+
this.logStream.write(`Started: ${now.toISOString()}\n`);
|
|
182
|
+
this.logStream.write(`${'='.repeat(80)}\n\n`);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// Silent fail - don't break UI if logging fails
|
|
185
|
+
this.logStream = null;
|
|
186
|
+
}
|
|
149
187
|
}
|
|
150
188
|
|
|
151
189
|
addLog(type, message) {
|
|
152
190
|
const timestamp = new Date().toLocaleTimeString();
|
|
153
191
|
this.logs.push({ timestamp, type, message });
|
|
154
192
|
if (this.logs.length > this.maxLogs) this.logs.shift();
|
|
193
|
+
|
|
194
|
+
// Write to log file
|
|
195
|
+
if (this.logStream) {
|
|
196
|
+
const isoTime = new Date().toISOString();
|
|
197
|
+
const logLine = `[${isoTime}] [${type.toUpperCase().padEnd(8)}] ${message}\n`;
|
|
198
|
+
this.logStream.write(logLine);
|
|
199
|
+
}
|
|
155
200
|
}
|
|
156
201
|
|
|
157
202
|
_line(text) {
|
|
@@ -329,7 +374,7 @@ class AlgoUI {
|
|
|
329
374
|
_drawLogs() {
|
|
330
375
|
const { W, logs, maxLogs } = this;
|
|
331
376
|
|
|
332
|
-
// Activity header
|
|
377
|
+
// Activity header with animated spinner
|
|
333
378
|
// Date is cached on first draw to prevent changes
|
|
334
379
|
if (!this.cachedDate) {
|
|
335
380
|
const nowDate = new Date();
|
|
@@ -337,9 +382,11 @@ class AlgoUI {
|
|
|
337
382
|
}
|
|
338
383
|
const dateStr = this.cachedDate;
|
|
339
384
|
|
|
340
|
-
//
|
|
341
|
-
const
|
|
342
|
-
|
|
385
|
+
// Animated spinner - advances on each render
|
|
386
|
+
const spinner = SPINNER[this.spinnerFrame % SPINNER.length];
|
|
387
|
+
this.spinnerFrame++;
|
|
388
|
+
|
|
389
|
+
const leftText = ` EXECUTION LOG ${spinner}`;
|
|
343
390
|
const rightText = `[X] STOP `;
|
|
344
391
|
|
|
345
392
|
const totalFixed = leftText.length + rightText.length;
|
|
@@ -347,7 +394,7 @@ class AlgoUI {
|
|
|
347
394
|
const centerPadLeft = Math.floor((centerSpace - dateStr.length) / 2);
|
|
348
395
|
const centerPadRight = centerSpace - dateStr.length - centerPadLeft;
|
|
349
396
|
|
|
350
|
-
const left = ` ${chalk.bold('EXECUTION LOG')} ${chalk.green(
|
|
397
|
+
const left = ` ${chalk.bold('EXECUTION LOG')} ${chalk.green(spinner)}`;
|
|
351
398
|
const center = ' '.repeat(Math.max(0, centerPadLeft)) + chalk.white.bold(dateStr) + ' '.repeat(Math.max(0, centerPadRight));
|
|
352
399
|
const right = chalk.yellow.bold('[X] STOP') + ' ';
|
|
353
400
|
|
|
@@ -443,6 +490,38 @@ class AlgoUI {
|
|
|
443
490
|
process.stdout.write('\x1B[?25h');
|
|
444
491
|
console.clear();
|
|
445
492
|
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Close log file with session summary
|
|
496
|
+
*/
|
|
497
|
+
closeLog(stats) {
|
|
498
|
+
if (this.logStream) {
|
|
499
|
+
try {
|
|
500
|
+
const now = new Date();
|
|
501
|
+
this.logStream.write(`\n${'='.repeat(80)}\n`);
|
|
502
|
+
this.logStream.write(`SESSION ENDED: ${now.toISOString()}\n`);
|
|
503
|
+
if (stats) {
|
|
504
|
+
this.logStream.write(`SUMMARY:\n`);
|
|
505
|
+
this.logStream.write(` Trades: ${stats.trades || 0}\n`);
|
|
506
|
+
this.logStream.write(` Wins: ${stats.wins || 0}\n`);
|
|
507
|
+
this.logStream.write(` Losses: ${stats.losses || 0}\n`);
|
|
508
|
+
this.logStream.write(` Session P&L: $${(stats.sessionPnl || 0).toFixed(2)}\n`);
|
|
509
|
+
this.logStream.write(` Duration: ${stats.duration || 'N/A'}\n`);
|
|
510
|
+
}
|
|
511
|
+
this.logStream.write(`${'='.repeat(80)}\n`);
|
|
512
|
+
this.logStream.end();
|
|
513
|
+
} catch (e) {
|
|
514
|
+
// Silent fail
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get log file path
|
|
521
|
+
*/
|
|
522
|
+
getLogFile() {
|
|
523
|
+
return this.logFile;
|
|
524
|
+
}
|
|
446
525
|
}
|
|
447
526
|
|
|
448
527
|
/**
|