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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.6.139",
3
+ "version": "2.6.141",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -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, show qty only if > 1)
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
- const qty = c.qty || 1;
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
 
@@ -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 - HF style (NO SPINNER/TIME - causes flicker on VPS/SSH)
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
- // Static indicator instead of spinner
341
- const indicator = '●';
342
- const leftText = ` EXECUTION LOG ${indicator}`;
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(indicator)}`;
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
  /**