hedgequantx 2.6.163 → 2.7.1

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.
Files changed (146) hide show
  1. package/README.md +15 -88
  2. package/bin/cli.js +0 -11
  3. package/dist/lib/api.jsc +0 -0
  4. package/dist/lib/api2.jsc +0 -0
  5. package/dist/lib/core.jsc +0 -0
  6. package/dist/lib/core2.jsc +0 -0
  7. package/dist/lib/data.js +1 -1
  8. package/dist/lib/data.jsc +0 -0
  9. package/dist/lib/data2.jsc +0 -0
  10. package/dist/lib/decoder.jsc +0 -0
  11. package/dist/lib/m/mod1.jsc +0 -0
  12. package/dist/lib/m/mod2.jsc +0 -0
  13. package/dist/lib/n/r1.jsc +0 -0
  14. package/dist/lib/n/r2.jsc +0 -0
  15. package/dist/lib/n/r3.jsc +0 -0
  16. package/dist/lib/n/r4.jsc +0 -0
  17. package/dist/lib/n/r5.jsc +0 -0
  18. package/dist/lib/n/r6.jsc +0 -0
  19. package/dist/lib/n/r7.jsc +0 -0
  20. package/dist/lib/o/util1.jsc +0 -0
  21. package/dist/lib/o/util2.jsc +0 -0
  22. package/package.json +8 -5
  23. package/src/app.js +40 -162
  24. package/src/config/constants.js +31 -33
  25. package/src/config/propfirms.js +13 -217
  26. package/src/config/settings.js +0 -43
  27. package/src/lib/api.js +198 -0
  28. package/src/lib/api2.js +353 -0
  29. package/src/lib/core.js +539 -0
  30. package/src/lib/core2.js +341 -0
  31. package/src/lib/data.js +555 -0
  32. package/src/lib/data2.js +492 -0
  33. package/src/lib/decoder.js +599 -0
  34. package/src/lib/m/s1.js +804 -0
  35. package/src/lib/m/s2.js +34 -0
  36. package/src/lib/n/r1.js +454 -0
  37. package/src/lib/n/r2.js +514 -0
  38. package/src/lib/n/r3.js +631 -0
  39. package/src/lib/n/r4.js +401 -0
  40. package/src/lib/n/r5.js +335 -0
  41. package/src/lib/n/r6.js +425 -0
  42. package/src/lib/n/r7.js +530 -0
  43. package/src/lib/o/l1.js +44 -0
  44. package/src/lib/o/l2.js +427 -0
  45. package/src/lib/python-bridge.js +206 -0
  46. package/src/menus/connect.js +14 -176
  47. package/src/menus/dashboard.js +65 -110
  48. package/src/pages/accounts.js +18 -18
  49. package/src/pages/algo/copy-trading.js +210 -240
  50. package/src/pages/algo/index.js +41 -104
  51. package/src/pages/algo/one-account.js +386 -33
  52. package/src/pages/algo/ui.js +312 -151
  53. package/src/pages/orders.js +3 -3
  54. package/src/pages/positions.js +3 -3
  55. package/src/pages/stats/chart.js +74 -0
  56. package/src/pages/stats/display.js +228 -0
  57. package/src/pages/stats/index.js +236 -0
  58. package/src/pages/stats/metrics.js +213 -0
  59. package/src/pages/user.js +6 -6
  60. package/src/services/hqx-server/constants.js +55 -0
  61. package/src/services/hqx-server/index.js +401 -0
  62. package/src/services/hqx-server/latency.js +81 -0
  63. package/src/services/index.js +12 -3
  64. package/src/services/rithmic/accounts.js +7 -32
  65. package/src/services/rithmic/connection.js +1 -204
  66. package/src/services/rithmic/contracts.js +116 -99
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +63 -120
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -111
  71. package/src/services/rithmic/protobuf.js +384 -138
  72. package/src/services/session.js +22 -173
  73. package/src/ui/box.js +10 -18
  74. package/src/ui/index.js +1 -3
  75. package/src/ui/menu.js +1 -1
  76. package/src/utils/prompts.js +2 -2
  77. package/dist/lib/m/s1.js +0 -1
  78. package/src/menus/ai-agent-connect.js +0 -181
  79. package/src/menus/ai-agent-models.js +0 -219
  80. package/src/menus/ai-agent-oauth.js +0 -292
  81. package/src/menus/ai-agent-ui.js +0 -141
  82. package/src/menus/ai-agent.js +0 -484
  83. package/src/pages/algo/algo-config.js +0 -195
  84. package/src/pages/algo/algo-multi.js +0 -801
  85. package/src/pages/algo/algo-utils.js +0 -58
  86. package/src/pages/algo/copy-engine.js +0 -449
  87. package/src/pages/algo/custom-strategy.js +0 -459
  88. package/src/pages/algo/logger.js +0 -245
  89. package/src/pages/algo/smart-logs-data.js +0 -218
  90. package/src/pages/algo/smart-logs.js +0 -387
  91. package/src/pages/algo/ui-constants.js +0 -144
  92. package/src/pages/algo/ui-summary.js +0 -184
  93. package/src/pages/stats-calculations.js +0 -191
  94. package/src/pages/stats-ui.js +0 -381
  95. package/src/pages/stats.js +0 -339
  96. package/src/services/ai/client-analysis.js +0 -194
  97. package/src/services/ai/client-models.js +0 -333
  98. package/src/services/ai/client.js +0 -343
  99. package/src/services/ai/index.js +0 -384
  100. package/src/services/ai/oauth-anthropic.js +0 -265
  101. package/src/services/ai/oauth-gemini.js +0 -223
  102. package/src/services/ai/oauth-iflow.js +0 -269
  103. package/src/services/ai/oauth-openai.js +0 -233
  104. package/src/services/ai/oauth-qwen.js +0 -279
  105. package/src/services/ai/providers/direct-providers.js +0 -323
  106. package/src/services/ai/providers/index.js +0 -62
  107. package/src/services/ai/providers/other-providers.js +0 -104
  108. package/src/services/ai/proxy-install.js +0 -249
  109. package/src/services/ai/proxy-manager.js +0 -494
  110. package/src/services/ai/proxy-remote.js +0 -161
  111. package/src/services/ai/strategy-supervisor.js +0 -1312
  112. package/src/services/ai/supervisor-data.js +0 -195
  113. package/src/services/ai/supervisor-optimize.js +0 -215
  114. package/src/services/ai/supervisor-sync.js +0 -178
  115. package/src/services/ai/supervisor-utils.js +0 -158
  116. package/src/services/ai/supervisor.js +0 -484
  117. package/src/services/ai/validation.js +0 -250
  118. package/src/services/hqx-server-events.js +0 -110
  119. package/src/services/hqx-server-handlers.js +0 -217
  120. package/src/services/hqx-server-latency.js +0 -136
  121. package/src/services/hqx-server.js +0 -403
  122. package/src/services/position-constants.js +0 -28
  123. package/src/services/position-exit-logic.js +0 -174
  124. package/src/services/position-manager.js +0 -438
  125. package/src/services/position-momentum.js +0 -206
  126. package/src/services/projectx/accounts.js +0 -142
  127. package/src/services/projectx/index.js +0 -443
  128. package/src/services/projectx/market.js +0 -172
  129. package/src/services/projectx/stats.js +0 -110
  130. package/src/services/projectx/trading.js +0 -180
  131. package/src/services/rithmic/latency-tracker.js +0 -182
  132. package/src/services/rithmic/market-data-decoders.js +0 -229
  133. package/src/services/rithmic/market-data.js +0 -272
  134. package/src/services/rithmic/orders-fast.js +0 -246
  135. package/src/services/rithmic/proto-decoders.js +0 -403
  136. package/src/services/rithmic/specs.js +0 -146
  137. package/src/services/rithmic/trade-history.js +0 -254
  138. package/src/services/session-history.js +0 -475
  139. package/src/services/strategy/hft-signal-calc.js +0 -147
  140. package/src/services/strategy/hft-tick.js +0 -407
  141. package/src/services/strategy/recovery-math.js +0 -402
  142. package/src/services/tradovate/constants.js +0 -109
  143. package/src/services/tradovate/index.js +0 -392
  144. package/src/services/tradovate/market.js +0 -47
  145. package/src/services/tradovate/orders.js +0 -145
  146. package/src/services/tradovate/websocket.js +0 -97
@@ -4,12 +4,86 @@
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');
10
7
 
11
- const { BOX, SPINNER, LOG_COLORS, LOG_ICONS, stripAnsi, colorizeMessage, center, fitToWidth, buildCell, checkMarketStatus } = require('./ui-constants');
12
- const { renderSessionSummary, renderMultiSymbolSummary } = require('./ui-summary');
8
+ // Box drawing characters
9
+ const BOX = {
10
+ TOP: '\u2554', BOT: '\u255A', V: '\u2551', H: '\u2550',
11
+ TR: '\u2557', BR: '\u255D', ML: '\u2560', MR: '\u2563',
12
+ TM: '\u2564', BM: '\u2567', MM: '\u256A', VS: '\u2502'
13
+ };
14
+
15
+ // Spinner characters
16
+ const SPINNER = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];
17
+
18
+ // Log type colors - HF grade
19
+ const LOG_COLORS = {
20
+ // Executions
21
+ fill_buy: chalk.green.bold,
22
+ fill_sell: chalk.red.bold,
23
+ fill_win: chalk.green.bold,
24
+ fill_loss: chalk.red.bold,
25
+ // Status
26
+ connected: chalk.green,
27
+ ready: chalk.cyan,
28
+ // Errors
29
+ error: chalk.red.bold,
30
+ reject: chalk.red,
31
+ // Info
32
+ info: chalk.gray,
33
+ system: chalk.blue
34
+ };
35
+
36
+ // Log type icons - compact HF style
37
+ const LOG_ICONS = {
38
+ fill_buy: 'BUY ',
39
+ fill_sell: 'SELL ',
40
+ fill_win: 'WIN ',
41
+ fill_loss: 'LOSS ',
42
+ connected: 'CONN ',
43
+ ready: 'READY',
44
+ error: 'ERR ',
45
+ reject: 'REJ ',
46
+ info: 'INFO ',
47
+ system: 'SYS '
48
+ };
49
+
50
+ /**
51
+ * Strip ANSI codes from string
52
+ */
53
+ const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*m/g, '');
54
+
55
+ /**
56
+ * Center text in width
57
+ */
58
+ const center = (text, width) => {
59
+ const pad = Math.floor((width - text.length) / 2);
60
+ return ' '.repeat(pad) + text + ' '.repeat(width - pad - text.length);
61
+ };
62
+
63
+ /**
64
+ * Fit text to exact width (truncate or pad)
65
+ */
66
+ const fitToWidth = (text, width) => {
67
+ const plain = stripAnsi(text);
68
+ if (plain.length > width) {
69
+ let count = 0, cut = 0;
70
+ for (let i = 0; i < text.length && count < width - 3; i++) {
71
+ if (text[i] === '\x1B') { while (i < text.length && text[i] !== 'm') i++; }
72
+ else { count++; cut = i + 1; }
73
+ }
74
+ return text.substring(0, cut) + '...';
75
+ }
76
+ return text + ' '.repeat(width - plain.length);
77
+ };
78
+
79
+ /**
80
+ * Build a labeled cell for grid
81
+ */
82
+ const buildCell = (label, value, color, width) => {
83
+ const text = ` ${label}: ${color(value)}`;
84
+ const plain = ` ${label}: ${value}`;
85
+ return { text, plain, padded: text + ' '.repeat(Math.max(0, width - plain.length)) };
86
+ };
13
87
 
14
88
  /**
15
89
  * Create AlgoUI renderer
@@ -17,198 +91,226 @@ const { renderSessionSummary, renderMultiSymbolSummary } = require('./ui-summary
17
91
  class AlgoUI {
18
92
  constructor(config) {
19
93
  this.config = config;
20
- this.W = 96;
94
+ this.W = 96; // Fixed width
21
95
  this.logs = [];
22
- this.maxLogs = 40;
96
+ this.maxLogs = 45; // Max visible logs
23
97
  this.spinnerFrame = 0;
24
98
  this.firstDraw = true;
25
99
  this.isDrawing = false;
26
- this.lines = [];
27
- this.lastOutput = '';
28
- this.lastStatsHash = '';
29
- this.lastLogsHash = '';
30
- this.lastSpinnerUpdate = 0;
31
- this.logFile = null;
32
- this.logStream = null;
33
- this._initLogFile();
34
- }
35
-
36
- _initLogFile() {
37
- try {
38
- const logsDir = path.join(os.homedir(), '.hqx', 'logs');
39
- if (!fs.existsSync(logsDir)) {
40
- fs.mkdirSync(logsDir, { recursive: true });
41
- }
42
- const now = new Date();
43
- const dateStr = now.toISOString().split('T')[0];
44
- const timeStr = now.toTimeString().split(' ')[0].replace(/:/g, '-');
45
- const mode = this.config.mode || 'algo';
46
- this.logFile = path.join(logsDir, `session_${mode}_${dateStr}_${timeStr}.log`);
47
- this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
48
- this.logStream.write(`\n${'='.repeat(80)}\n`);
49
- this.logStream.write(`HQX ALGO SESSION LOG\n`);
50
- this.logStream.write(`Mode: ${this.config.mode || 'unknown'}\n`);
51
- this.logStream.write(`Started: ${now.toISOString()}\n`);
52
- this.logStream.write(`${'='.repeat(80)}\n\n`);
53
- } catch (e) {
54
- this.logStream = null;
55
- }
100
+ this.buffer = '';
56
101
  }
57
102
 
58
103
  addLog(type, message) {
59
104
  const timestamp = new Date().toLocaleTimeString();
60
105
  this.logs.push({ timestamp, type, message });
61
106
  if (this.logs.length > this.maxLogs) this.logs.shift();
62
- if (this.logStream) {
63
- const isoTime = new Date().toISOString();
64
- const logLine = `[${isoTime}] [${type.toUpperCase().padEnd(8)}] ${message}\n`;
65
- this.logStream.write(logLine);
66
- }
67
107
  }
68
108
 
69
- _line(text) { this.lines.push(text); }
109
+ _line(text) {
110
+ this.buffer += text + '\x1B[K\n';
111
+ }
70
112
 
71
113
  _drawHeader() {
72
114
  const { W } = this;
73
115
  const version = require('../../../package.json').version;
116
+
117
+ // Top border
74
118
  this._line(chalk.cyan(BOX.TOP + BOX.H.repeat(W) + BOX.TR));
119
+
120
+ // Logo (compact)
75
121
  this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗') + chalk.yellow('██╗ ██╗') + ' ' + chalk.cyan(BOX.V));
76
122
  this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝') + chalk.yellow('╚██╗██╔╝') + ' ' + chalk.cyan(BOX.V));
77
123
  this._line(chalk.cyan(BOX.V) + chalk.cyan(' ███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ') + chalk.yellow(' ╚███╔╝ ') + ' ' + chalk.cyan(BOX.V));
78
124
  this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ') + chalk.yellow(' ██╔██╗ ') + ' ' + chalk.cyan(BOX.V));
79
125
  this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ') + chalk.yellow('██╔╝ ██╗') + ' ' + chalk.cyan(BOX.V));
80
126
  this._line(chalk.cyan(BOX.V) + chalk.cyan(' ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ') + chalk.yellow('╚═╝ ╚═╝') + ' ' + chalk.cyan(BOX.V));
127
+
128
+ // Separator + title
81
129
  this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
82
- this._line(chalk.cyan(BOX.V) + chalk.white(center(`PROP FUTURES ALGO TRADING v${version}`, W)) + chalk.cyan(BOX.V));
130
+ this._line(chalk.cyan(BOX.V) + chalk.white(center(`Prop Futures Algo Trading v${version}`, W)) + chalk.cyan(BOX.V));
83
131
  this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
84
- this._line(chalk.cyan(BOX.V) + chalk.yellow.bold(center((this.config.subtitle || 'HQX ALGO TRADING').toUpperCase(), W)) + chalk.cyan(BOX.V));
132
+ this._line(chalk.cyan(BOX.V) + chalk.yellow(center(this.config.subtitle || 'HQX Algo Trading', W)) + chalk.cyan(BOX.V));
85
133
  }
86
134
 
87
135
  _drawStats(stats) {
136
+ const { W } = this;
88
137
  const isCopyTrading = this.config.mode === 'copy-trading';
89
- if (isCopyTrading) this._drawCopyTradingStats(stats);
90
- else this._drawOneAccountStats(stats);
138
+
139
+ const pnl = stats.pnl !== null && stats.pnl !== undefined ? stats.pnl : null;
140
+ const pnlColor = pnl === null ? chalk.gray : (pnl >= 0 ? chalk.green : chalk.red);
141
+ const pnlStr = pnl === null ? '--' : ((pnl >= 0 ? '+$' : '-$') + Math.abs(pnl).toFixed(2));
142
+ const latencyColor = stats.latency < 100 ? chalk.green : (stats.latency < 300 ? chalk.yellow : chalk.red);
143
+ const serverColor = stats.connected ? chalk.green : chalk.red;
144
+
145
+ if (isCopyTrading) {
146
+ this._drawCopyTradingStats(stats, pnlColor, pnlStr, latencyColor, serverColor);
147
+ } else {
148
+ this._drawOneAccountStats(stats, pnlColor, pnlStr, latencyColor, serverColor);
149
+ }
91
150
  }
92
151
 
93
- _drawOneAccountStats(stats) {
152
+ _drawOneAccountStats(stats, pnlColor, pnlStr, latencyColor, serverColor) {
94
153
  const { W } = this;
95
154
  const colL = 48, colR = 47;
96
155
  const pad = (len) => ' '.repeat(Math.max(0, len));
156
+
97
157
  const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
98
158
  const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
99
159
  const GB = BOX.ML + BOX.H.repeat(colL) + BOX.BM + BOX.H.repeat(colR) + BOX.MR;
100
- const row = (c1, c2) => { this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V)); };
101
-
160
+
161
+ const row = (c1, c2) => {
162
+ this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V));
163
+ };
164
+
102
165
  this._line(chalk.cyan(GT));
166
+
167
+ // Row 1: Account | Symbol
103
168
  const accountName = String(stats.accountName || 'N/A').substring(0, 40);
104
169
  const symbol = String(stats.symbol || 'N/A').substring(0, 35);
105
- row(buildCell('Account', accountName, chalk.cyan, colL).padded, buildCell('Symbol', symbol, chalk.yellow, colR).padded);
170
+ const r1c1 = buildCell('Account', accountName, chalk.cyan, colL);
171
+ const r1c2 = buildCell('Symbol', symbol, chalk.yellow, colR);
172
+ row(r1c1.padded, r1c2.padded);
106
173
 
107
174
  this._line(chalk.cyan(GM));
108
- const openPnl = stats.openPnl;
109
- const closedPnl = stats.closedPnl;
110
- const openPnlStr = openPnl === null || openPnl === undefined ? '--' : (openPnl >= 0 ? `+$${openPnl.toFixed(2)}` : `-$${Math.abs(openPnl).toFixed(2)}`);
111
- const closedPnlStr = closedPnl === null || closedPnl === undefined ? '--' : (closedPnl >= 0 ? `+$${closedPnl.toFixed(2)}` : `-$${Math.abs(closedPnl).toFixed(2)}`);
112
- const openPnlColor = (openPnl || 0) >= 0 ? chalk.green : chalk.red;
113
- const closedPnlColor = (closedPnl || 0) >= 0 ? chalk.green : chalk.red;
114
- row(buildCell('OPEN P&L', openPnlStr, openPnlColor, colL).padded, buildCell('CLOSED P&L', closedPnlStr, closedPnlColor, colR).padded);
175
+
176
+ // Row 2: Qty | P&L
177
+ const r2c1 = buildCell('Qty', (stats.qty || '1').toString(), chalk.cyan, colL);
178
+ const r2c2 = buildCell('P&L', pnlStr, pnlColor, colR);
179
+ row(r2c1.padded, r2c2.padded);
115
180
 
116
181
  this._line(chalk.cyan(GM));
182
+
183
+ // Row 3: Target | Risk
117
184
  const targetStr = stats.target !== null && stats.target !== undefined ? '$' + stats.target.toFixed(2) : '--';
118
185
  const riskStr = stats.risk !== null && stats.risk !== undefined ? '$' + stats.risk.toFixed(2) : '--';
119
- row(buildCell('TARGET', targetStr, chalk.green, colL).padded, buildCell('RISK', riskStr, chalk.red, colR).padded);
186
+ const r3c1 = buildCell('Target', targetStr, chalk.green, colL);
187
+ const r3c2 = buildCell('Risk', riskStr, chalk.red, colR);
188
+ row(r3c1.padded, r3c2.padded);
120
189
 
121
190
  this._line(chalk.cyan(GM));
122
- const r4c1t = ` ${chalk.bold('TRADES')}: ${chalk.cyan.bold(stats.trades || 0)} ${chalk.bold('W/L')}: ${chalk.green.bold(stats.wins || 0)}/${chalk.red.bold(stats.losses || 0)}`;
123
- const r4c1p = ` TRADES: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
124
- row(r4c1t + pad(colL - r4c1p.length), buildCell('PROPFIRM', stats.propfirm || 'N/A', chalk.cyan, colR).padded);
191
+
192
+ // Row 4: Trades | Latency (API response time)
193
+ const r4c1t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
194
+ const r4c1p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
195
+ const r4c2 = buildCell('Latency', `${stats.latency || 0}ms`, latencyColor, colR);
196
+ row(r4c1t + pad(colL - r4c1p.length), r4c2.padded);
125
197
 
126
198
  this._line(chalk.cyan(GM));
127
- const connectionType = stats.platform || stats.connection || 'N/A';
128
- const connectionColor = connectionType.toLowerCase().includes('rithmic') ? chalk.green : chalk.yellow;
129
- const agentCount = stats.agentCount || 0;
130
- const agentStr = agentCount > 0 ? `${agentCount} agent${agentCount > 1 ? 's' : ''} active` : 'None';
131
- const agentColor = agentCount > 0 ? chalk.green : chalk.gray;
132
- row(buildCell('CONNECTION', connectionType, connectionColor, colL).padded, buildCell('AGENTS', agentStr, agentColor, colR).padded);
199
+
200
+ // Row 5: Connection | Propfirm
201
+ const connection = stats.platform || 'Rithmic';
202
+ const r5c1 = buildCell('Connection', connection, chalk.white, colL);
203
+ const r5c2 = buildCell('Propfirm', stats.propfirm || 'N/A', chalk.cyan, colR);
204
+ row(r5c1.padded, r5c2.padded);
133
205
 
134
206
  this._line(chalk.cyan(GB));
135
207
  }
136
208
 
137
- _drawCopyTradingStats(stats) {
209
+ _drawCopyTradingStats(stats, pnlColor, pnlStr, latencyColor, serverColor) {
138
210
  const { W } = this;
139
211
  const colL = 48, colR = 47;
140
212
  const pad = (len) => ' '.repeat(Math.max(0, len));
213
+
141
214
  const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
142
215
  const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
143
216
  const GB = BOX.ML + BOX.H.repeat(colL) + BOX.BM + BOX.H.repeat(colR) + BOX.MR;
144
- const GF = BOX.ML + BOX.H.repeat(W) + BOX.MR;
145
- const row = (c1, c2) => { this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V)); };
146
-
217
+
218
+ const row = (c1, c2) => {
219
+ this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V));
220
+ };
221
+
147
222
  this._line(chalk.cyan(GT));
223
+
224
+ // Row 1: Lead Account | Follower Account
148
225
  const leadName = (stats.leadName || 'N/A').substring(0, 40);
149
226
  const followerName = (stats.followerName || 'N/A').substring(0, 40);
150
- row(buildCell('Lead', leadName, chalk.cyan, colL).padded, buildCell('Follower', followerName, chalk.magenta, colR).padded);
227
+ const r1c1 = buildCell('Lead', leadName, chalk.cyan, colL);
228
+ const r1c2 = buildCell('Follower', followerName, chalk.magenta, colR);
229
+ row(r1c1.padded, r1c2.padded);
230
+
231
+ // Full width separator
232
+ const GF = BOX.ML + BOX.H.repeat(W) + BOX.MR;
151
233
 
152
234
  this._line(chalk.cyan(GF));
235
+
236
+ // Row 2: Symbol (centered, single row)
153
237
  const symbol = (stats.symbol || stats.leadSymbol || 'N/A').substring(0, 60);
154
- const symbolText = `SYMBOL: ${symbol}`;
155
- this._line(chalk.cyan(BOX.V) + chalk.yellow(center(symbolText, W)) + chalk.cyan(BOX.V));
238
+ const symbolText = `Symbol: ${symbol}`;
239
+ const symbolPadded = center(symbolText, W);
240
+ this._line(chalk.cyan(BOX.V) + chalk.yellow(symbolPadded) + chalk.cyan(BOX.V));
156
241
 
157
242
  this._line(chalk.cyan(GT));
158
- row(buildCell('QTY', (stats.leadQty || '1').toString(), chalk.cyan, colL).padded, buildCell('QTY', (stats.followerQty || '1').toString(), chalk.cyan, colR).padded);
243
+
244
+ // Row 3: Lead Qty | Follower Qty
245
+ const r3c1 = buildCell('Qty', (stats.leadQty || '1').toString(), chalk.cyan, colL);
246
+ const r3c2 = buildCell('Qty', (stats.followerQty || '1').toString(), chalk.cyan, colR);
247
+ row(r3c1.padded, r3c2.padded);
159
248
 
160
249
  this._line(chalk.cyan(GM));
161
- row(buildCell('TARGET', '$' + (stats.target || 0).toFixed(2), chalk.green, colL).padded, buildCell('RISK', '$' + (stats.risk || 0).toFixed(2), chalk.red, colR).padded);
250
+
251
+ // Row 4: Target | Risk
252
+ const r4c1 = buildCell('Target', '$' + (stats.target || 0).toFixed(2), chalk.green, colL);
253
+ const r4c2 = buildCell('Risk', '$' + (stats.risk || 0).toFixed(2), chalk.red, colR);
254
+ row(r4c1.padded, r4c2.padded);
162
255
 
163
256
  this._line(chalk.cyan(GM));
164
- const pnl = stats.pnl !== null && stats.pnl !== undefined ? stats.pnl : null;
165
- const pnlColor = pnl === null ? chalk.gray : (pnl >= 0 ? chalk.green : chalk.red);
166
- const pnlStr = pnl === null ? '--' : ((pnl >= 0 ? '+$' : '-$') + Math.abs(pnl).toFixed(2));
167
- const r5c2t = ` ${chalk.bold('TRADES')}: ${chalk.cyan.bold(stats.trades || 0)} ${chalk.bold('W/L')}: ${chalk.green.bold(stats.wins || 0)}/${chalk.red.bold(stats.losses || 0)}`;
168
- const r5c2p = ` TRADES: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
169
- row(buildCell('P&L', pnlStr, pnlColor, colL).padded, r5c2t + pad(colR - r5c2p.length));
257
+
258
+ // Row 5: P&L | Trades
259
+ const r5c1 = buildCell('P&L', pnlStr, pnlColor, colL);
260
+ const r5c2t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
261
+ const r5c2p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
262
+ row(r5c1.padded, r5c2t + pad(colR - r5c2p.length));
170
263
 
171
264
  this._line(chalk.cyan(GB));
172
265
  }
173
266
 
174
267
  _drawLogs() {
175
268
  const { W, logs, maxLogs } = this;
176
- if (!this.cachedDate) {
177
- this.cachedDate = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).toUpperCase();
178
- }
179
- const dateStr = this.cachedDate;
180
- const spinner = SPINNER[this.spinnerFrame % SPINNER.length];
181
- this.spinnerFrame++;
269
+
270
+ // Activity header - HF style
271
+ this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER.length;
272
+ const spinner = SPINNER[this.spinnerFrame];
273
+ const now = new Date();
274
+ const timeStr = now.toLocaleTimeString('en-US', { hour12: false });
275
+ const dateStr = now.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
182
276
 
183
277
  const leftText = ` EXECUTION LOG ${spinner}`;
184
278
  const rightText = `[X] STOP `;
279
+
185
280
  const totalFixed = leftText.length + rightText.length;
186
281
  const centerSpace = W - totalFixed;
187
282
  const centerPadLeft = Math.floor((centerSpace - dateStr.length) / 2);
188
283
  const centerPadRight = centerSpace - dateStr.length - centerPadLeft;
189
284
 
190
- const left = ` ${chalk.bold('EXECUTION LOG')} ${chalk.green(spinner)}`;
191
- const ctr = ' '.repeat(Math.max(0, centerPadLeft)) + chalk.white.bold(dateStr) + ' '.repeat(Math.max(0, centerPadRight));
192
- const right = chalk.yellow.bold('[X] STOP') + ' ';
285
+ const left = ` EXECUTION LOG ${chalk.yellow(spinner)}`;
286
+ const center = ' '.repeat(Math.max(0, centerPadLeft)) + chalk.white(dateStr) + ' '.repeat(Math.max(0, centerPadRight));
287
+ const right = chalk.yellow('[X] STOP') + ' ';
193
288
 
194
- this._line(chalk.cyan(BOX.V) + chalk.white(left) + ctr + right + chalk.cyan(BOX.V));
289
+ this._line(chalk.cyan(BOX.V) + chalk.white.bold(left) + center + right + chalk.cyan(BOX.V));
195
290
  this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
196
291
 
292
+ // Logs: newest at top
197
293
  const visible = logs.slice(-maxLogs).reverse();
294
+
198
295
  if (visible.length === 0) {
199
296
  this._line(chalk.cyan(BOX.V) + chalk.gray(fitToWidth(' Awaiting market signals...', W)) + chalk.cyan(BOX.V));
200
- for (let i = 0; i < maxLogs - 1; i++) this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
297
+ for (let i = 0; i < maxLogs - 1; i++) {
298
+ this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
299
+ }
201
300
  } else {
202
301
  visible.forEach(log => {
203
302
  const color = LOG_COLORS[log.type] || chalk.gray;
204
- const icon = LOG_ICONS[log.type] || '➡ INFO ';
205
- const coloredIcon = color.bold(icon);
206
- const coloredMessage = colorizeMessage(log.message);
207
- const line = ` ${chalk.gray(log.timestamp)} ${coloredIcon} ${coloredMessage}`;
303
+ const icon = LOG_ICONS[log.type] || '';
304
+ // HF style: TIME | TYPE | MESSAGE
305
+ const line = ` ${chalk.gray(log.timestamp)} ${color(icon)}${log.message}`;
208
306
  this._line(chalk.cyan(BOX.V) + fitToWidth(line, W) + chalk.cyan(BOX.V));
209
307
  });
210
- for (let i = visible.length; i < maxLogs; i++) this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
308
+ for (let i = visible.length; i < maxLogs; i++) {
309
+ this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
310
+ }
211
311
  }
312
+
313
+ // Bottom border
212
314
  this._line(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
213
315
  }
214
316
 
@@ -216,64 +318,123 @@ class AlgoUI {
216
318
  if (this.isDrawing) return;
217
319
  this.isDrawing = true;
218
320
 
219
- const statsHash = JSON.stringify({ pnl: stats.pnl, openPnl: stats.openPnl, closedPnl: stats.closedPnl, position: stats.position, trades: stats.trades, wins: stats.wins, losses: stats.losses, connected: stats.connected });
220
- const logsHash = this.logs.length + (this.logs[this.logs.length - 1]?.message || '');
221
- const hasChanges = statsHash !== this.lastStatsHash || logsHash !== this.lastLogsHash;
222
-
223
- if (this.firstDraw || hasChanges) {
224
- this.lastStatsHash = statsHash;
225
- this.lastLogsHash = logsHash;
226
- this.lines = [];
227
-
228
- if (this.firstDraw) {
229
- console.clear();
230
- process.stdout.write('\x1B[?25l');
231
- this.firstDraw = false;
232
- }
233
-
234
- this._line('');
235
- this._drawHeader();
236
- this._drawStats(stats);
237
- this._drawLogs();
238
-
239
- const output = this.lines.join('\n');
240
- if (output !== this.lastOutput) {
241
- const lines = this.lines;
242
- for (let i = 0; i < lines.length; i++) {
243
- process.stdout.write(`\x1B[${i + 1};1H${lines[i]}\x1B[K`);
244
- }
245
- this.lastOutput = output;
246
- }
321
+ this.buffer = '';
322
+
323
+ if (this.firstDraw) {
324
+ this.buffer += '\x1B[?1049h\x1B[?25l\x1B[2J';
325
+ this.firstDraw = false;
247
326
  }
327
+
328
+ this.buffer += '\x1B[H';
329
+ this._line('');
330
+ this._drawHeader();
331
+ this._drawStats(stats);
332
+ this._drawLogs();
333
+
334
+ process.stdout.write(this.buffer);
248
335
  this.isDrawing = false;
249
336
  }
250
337
 
251
338
  cleanup() {
252
- process.stdout.write('\x1B[?25h');
253
- console.clear();
339
+ process.stdout.write('\x1B[?1049l\x1B[?25h');
254
340
  }
341
+ }
342
+
343
+ /**
344
+ * Check market hours
345
+ */
346
+ const checkMarketStatus = () => {
347
+ const now = new Date();
348
+ const utcDay = now.getUTCDay();
349
+ const utcHour = now.getUTCHours();
350
+ const isDST = now.getTimezoneOffset() < Math.max(
351
+ new Date(now.getFullYear(), 0, 1).getTimezoneOffset(),
352
+ new Date(now.getFullYear(), 6, 1).getTimezoneOffset()
353
+ );
354
+ const ctOffset = isDST ? 5 : 6;
355
+ const ctHour = (utcHour - ctOffset + 24) % 24;
356
+ const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
357
+
358
+ if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
359
+ if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
360
+ if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
361
+ if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance' };
362
+ return { isOpen: true, message: 'Market OPEN' };
363
+ };
364
+
365
+ /**
366
+ * Render Session Summary - Same style as dashboard
367
+ */
368
+ const renderSessionSummary = (stats, stopReason) => {
369
+ const W = 96; // Same width as dashboard
370
+ const colL = Math.floor(W / 2) - 1;
371
+ const colR = W - colL - 1;
372
+ const version = require('../../../package.json').version;
255
373
 
256
- closeLog(stats) {
257
- if (this.logStream) {
258
- try {
259
- const now = new Date();
260
- this.logStream.write(`\n${'='.repeat(80)}\n`);
261
- this.logStream.write(`SESSION ENDED: ${now.toISOString()}\n`);
262
- if (stats) {
263
- this.logStream.write(`SUMMARY:\n`);
264
- this.logStream.write(` Trades: ${stats.trades || 0}\n`);
265
- this.logStream.write(` Wins: ${stats.wins || 0}\n`);
266
- this.logStream.write(` Losses: ${stats.losses || 0}\n`);
267
- this.logStream.write(` Session P&L: $${(stats.sessionPnl || 0).toFixed(2)}\n`);
268
- this.logStream.write(` Duration: ${stats.duration || 'N/A'}\n`);
269
- }
270
- this.logStream.write(`${'='.repeat(80)}\n`);
271
- this.logStream.end();
272
- } catch (e) {}
273
- }
274
- }
374
+ console.clear();
375
+ console.log();
275
376
 
276
- getLogFile() { return this.logFile; }
277
- }
377
+ // Top border
378
+ console.log(chalk.cyan(BOX.TOP + BOX.H.repeat(W) + BOX.TR));
379
+
380
+ // Logo
381
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗') + chalk.yellow('██╗ ██╗') + ' ' + chalk.cyan(BOX.V));
382
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝') + chalk.yellow('╚██╗██╔╝') + ' ' + chalk.cyan(BOX.V));
383
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ') + chalk.yellow(' ╚███╔╝ ') + ' ' + chalk.cyan(BOX.V));
384
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ') + chalk.yellow(' ██╔██╗ ') + ' ' + chalk.cyan(BOX.V));
385
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ') + chalk.yellow('██╔╝ ██╗') + ' ' + chalk.cyan(BOX.V));
386
+ console.log(chalk.cyan(BOX.V) + chalk.cyan(' ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ') + chalk.yellow('╚═╝ ╚═╝') + ' ' + chalk.cyan(BOX.V));
387
+
388
+ // Separator + title
389
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
390
+ console.log(chalk.cyan(BOX.V) + chalk.white(center(`Prop Futures Algo Trading v${version}`, W)) + chalk.cyan(BOX.V));
391
+ console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
392
+ console.log(chalk.cyan(BOX.V) + chalk.yellow(center('Session Summary', W)) + chalk.cyan(BOX.V));
393
+
394
+ // Grid separators
395
+ const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
396
+ const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
397
+
398
+ const row = (label1, value1, color1, label2, value2, color2) => {
399
+ const c1 = ` ${label1}: ${color1(value1)}`;
400
+ const c2 = ` ${label2}: ${color2(value2)}`;
401
+ const p1 = ` ${label1}: ${value1}`;
402
+ const p2 = ` ${label2}: ${value2}`;
403
+ const padded1 = c1 + ' '.repeat(Math.max(0, colL - p1.length));
404
+ const padded2 = c2 + ' '.repeat(Math.max(0, colR - p2.length));
405
+ console.log(chalk.cyan(BOX.V) + padded1 + chalk.cyan(BOX.VS) + padded2 + chalk.cyan(BOX.V));
406
+ };
407
+
408
+ console.log(chalk.cyan(GT));
409
+
410
+ // Row 1: Stop Reason | Duration
411
+ const duration = stats.duration || '--';
412
+ const reasonColor = stopReason === 'target' ? chalk.green : stopReason === 'risk' ? chalk.red : chalk.yellow;
413
+ row('Stop Reason', (stopReason || 'manual').toUpperCase(), reasonColor, 'Duration', duration, chalk.white);
414
+
415
+ console.log(chalk.cyan(GM));
416
+
417
+ // Row 2: Trades | Win Rate
418
+ const winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) + '%' : '0%';
419
+ row('Trades', String(stats.trades || 0), chalk.white, 'Win Rate', winRate, stats.wins >= stats.losses ? chalk.green : chalk.red);
420
+
421
+ console.log(chalk.cyan(GM));
422
+
423
+ // Row 3: Wins | Losses
424
+ row('Wins', String(stats.wins || 0), chalk.green, 'Losses', String(stats.losses || 0), chalk.red);
425
+
426
+ console.log(chalk.cyan(GM));
427
+
428
+ // Row 4: P&L | Target
429
+ const pnl = stats.pnl || 0;
430
+ const pnlStr = `${pnl >= 0 ? '+' : ''}$${Math.abs(pnl).toFixed(2)}`;
431
+ const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
432
+ const targetStr = `$${(stats.target || 0).toFixed(2)}`;
433
+ row('P&L', pnlStr, pnlColor, 'Target', targetStr, chalk.cyan);
434
+
435
+ // Bottom border
436
+ console.log(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
437
+ console.log();
438
+ };
278
439
 
279
- module.exports = { AlgoUI, checkMarketStatus, renderSessionSummary, renderMultiSymbolSummary, LOG_COLORS, LOG_ICONS, stripAnsi, center, fitToWidth };
440
+ module.exports = { AlgoUI, checkMarketStatus, renderSessionSummary, LOG_COLORS, LOG_ICONS, stripAnsi, center, fitToWidth };
@@ -19,14 +19,14 @@ const showOrders = async (service) => {
19
19
 
20
20
  try {
21
21
  // Step 1: Get connections
22
- spinner = ora({ text: 'LOADING CONNECTIONS...', color: 'yellow' }).start();
22
+ spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
23
23
 
24
24
  const allConns = connections.count() > 0
25
25
  ? connections.getAll()
26
26
  : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
27
27
 
28
28
  if (allConns.length === 0) {
29
- spinner.fail('NO CONNECTIONS FOUND');
29
+ spinner.fail('No connections found');
30
30
  await prompts.waitForEnter();
31
31
  return;
32
32
  }
@@ -90,7 +90,7 @@ const showOrders = async (service) => {
90
90
  }
91
91
  }
92
92
 
93
- spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
93
+ spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
94
  spinner.succeed(`Total: ${allOrders.length} order(s)`);
95
95
  console.log();
96
96
 
@@ -19,14 +19,14 @@ const showPositions = async (service) => {
19
19
 
20
20
  try {
21
21
  // Step 1: Get connections
22
- spinner = ora({ text: 'LOADING CONNECTIONS...', color: 'yellow' }).start();
22
+ spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
23
23
 
24
24
  const allConns = connections.count() > 0
25
25
  ? connections.getAll()
26
26
  : (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
27
27
 
28
28
  if (allConns.length === 0) {
29
- spinner.fail('NO CONNECTIONS FOUND');
29
+ spinner.fail('No connections found');
30
30
  await prompts.waitForEnter();
31
31
  return;
32
32
  }
@@ -90,7 +90,7 @@ const showPositions = async (service) => {
90
90
  }
91
91
  }
92
92
 
93
- spinner = ora({ text: 'PREPARING DISPLAY...', color: 'yellow' }).start();
93
+ spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
94
94
  spinner.succeed(`Total: ${allPositions.length} position(s)`);
95
95
  console.log();
96
96