hedgequantx 2.6.139 → 2.6.140

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.140",
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) {
@@ -443,6 +488,38 @@ class AlgoUI {
443
488
  process.stdout.write('\x1B[?25h');
444
489
  console.clear();
445
490
  }
491
+
492
+ /**
493
+ * Close log file with session summary
494
+ */
495
+ closeLog(stats) {
496
+ if (this.logStream) {
497
+ try {
498
+ const now = new Date();
499
+ this.logStream.write(`\n${'='.repeat(80)}\n`);
500
+ this.logStream.write(`SESSION ENDED: ${now.toISOString()}\n`);
501
+ if (stats) {
502
+ this.logStream.write(`SUMMARY:\n`);
503
+ this.logStream.write(` Trades: ${stats.trades || 0}\n`);
504
+ this.logStream.write(` Wins: ${stats.wins || 0}\n`);
505
+ this.logStream.write(` Losses: ${stats.losses || 0}\n`);
506
+ this.logStream.write(` Session P&L: $${(stats.sessionPnl || 0).toFixed(2)}\n`);
507
+ this.logStream.write(` Duration: ${stats.duration || 'N/A'}\n`);
508
+ }
509
+ this.logStream.write(`${'='.repeat(80)}\n`);
510
+ this.logStream.end();
511
+ } catch (e) {
512
+ // Silent fail
513
+ }
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Get log file path
519
+ */
520
+ getLogFile() {
521
+ return this.logFile;
522
+ }
446
523
  }
447
524
 
448
525
  /**