hedgequantx 1.8.13 → 1.8.15

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": "1.8.13",
3
+ "version": "1.8.15",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -213,7 +213,7 @@ const launchCopyTrading = async (config) => {
213
213
  const leadName = showNames ? (lead.account.accountName || lead.account.accountId) : 'HQX Lead *****';
214
214
  const followerName = showNames ? (follower.account.accountName || follower.account.accountId) : 'HQX Follower *****';
215
215
 
216
- const ui = new AlgoUI({ subtitle: 'HQX Copy Trading' });
216
+ const ui = new AlgoUI({ subtitle: 'HQX Copy Trading', mode: 'copy-trading' });
217
217
 
218
218
  const stats = {
219
219
  leadName, followerName,
@@ -71,34 +71,27 @@ const oneAccountMenu = async (service) => {
71
71
  };
72
72
 
73
73
  /**
74
- * Symbol selection
74
+ * Symbol selection - same as copy-trading
75
75
  */
76
76
  const selectSymbol = async (service, account) => {
77
- const spinner = ora({ text: 'Loading contracts...', color: 'yellow' }).start();
77
+ const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
78
78
 
79
79
  const contractsResult = await service.getContracts();
80
- if (!contractsResult.success) {
80
+ if (!contractsResult.success || !contractsResult.contracts?.length) {
81
81
  spinner.fail('Failed to load contracts');
82
82
  return null;
83
83
  }
84
84
 
85
- spinner.succeed('Contracts loaded');
85
+ // Normalize contract structure - API returns { name: "ESH6", description: "E-mini S&P 500..." }
86
+ const contracts = contractsResult.contracts.map(c => ({
87
+ ...c,
88
+ symbol: c.name || c.symbol,
89
+ name: c.description || c.name || c.symbol
90
+ }));
86
91
 
87
- // Group by category
88
- const categories = {};
89
- for (const c of contractsResult.contracts) {
90
- const cat = c.group || 'Other';
91
- if (!categories[cat]) categories[cat] = [];
92
- categories[cat].push(c);
93
- }
92
+ spinner.succeed(`Found ${contracts.length} contracts`);
94
93
 
95
- // Flatten with categories as hints
96
- const options = [];
97
- for (const [cat, contracts] of Object.entries(categories)) {
98
- for (const c of contracts.slice(0, 10)) {
99
- options.push({ label: `[${cat}] ${c.name || c.symbol}`, value: c });
100
- }
101
- }
94
+ const options = contracts.map(c => ({ label: c.name, value: c }));
102
95
  options.push({ label: '< Back', value: 'back' });
103
96
 
104
97
  const contract = await prompts.selectOption('Select Symbol:', options);
@@ -139,7 +132,7 @@ const launchAlgo = async (service, account, contract, config) => {
139
132
  const accountName = showName ? (account.accountName || account.accountId) : 'HQX *****';
140
133
  const symbolName = contract.name || contract.symbol;
141
134
 
142
- const ui = new AlgoUI({ subtitle: 'HQX Ultra-Scalping' });
135
+ const ui = new AlgoUI({ subtitle: 'HQX Ultra-Scalping', mode: 'one-account' });
143
136
 
144
137
  const stats = {
145
138
  accountName, symbol: symbolName, contracts,
@@ -126,20 +126,83 @@ class AlgoUI {
126
126
 
127
127
  _drawStats(stats) {
128
128
  const { W } = this;
129
- const colL = 48, colR = 47;
130
- const pad = (len) => ' '.repeat(Math.max(0, len));
129
+ const isCopyTrading = this.config.mode === 'copy-trading';
131
130
 
132
131
  const pnlColor = stats.pnl >= 0 ? chalk.green : chalk.red;
133
132
  const pnlStr = (stats.pnl >= 0 ? '+$' : '-$') + Math.abs(stats.pnl).toFixed(2);
134
133
  const latencyColor = stats.latency < 100 ? chalk.green : (stats.latency < 300 ? chalk.yellow : chalk.red);
135
134
  const serverColor = stats.connected ? chalk.green : chalk.red;
136
135
 
137
- // Grid borders
136
+ if (isCopyTrading) {
137
+ this._drawCopyTradingStats(stats, pnlColor, pnlStr, latencyColor, serverColor);
138
+ } else {
139
+ this._drawOneAccountStats(stats, pnlColor, pnlStr, latencyColor, serverColor);
140
+ }
141
+ }
142
+
143
+ _drawOneAccountStats(stats, pnlColor, pnlStr, latencyColor, serverColor) {
144
+ const { W } = this;
145
+ const colL = 48, colR = 47;
146
+ const pad = (len) => ' '.repeat(Math.max(0, len));
147
+
148
+ const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
149
+ const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
150
+ const GB = BOX.ML + BOX.H.repeat(colL) + BOX.BM + BOX.H.repeat(colR) + BOX.MR;
151
+
152
+ const row = (c1, c2) => {
153
+ this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V));
154
+ };
155
+
156
+ this._line(chalk.cyan(GT));
157
+
158
+ // Row 1: Account | Symbol
159
+ const accountName = (stats.accountName || 'N/A').substring(0, 40);
160
+ const symbol = (stats.symbol || 'N/A').substring(0, 35);
161
+ const r1c1 = buildCell('Account', accountName, chalk.cyan, colL);
162
+ const r1c2 = buildCell('Symbol', symbol, chalk.yellow, colR);
163
+ row(r1c1.padded, r1c2.padded);
164
+
165
+ this._line(chalk.cyan(GM));
166
+
167
+ // Row 2: Qty | P&L
168
+ const r2c1 = buildCell('Qty', (stats.qty || '1').toString(), chalk.cyan, colL);
169
+ const r2c2 = buildCell('P&L', pnlStr, pnlColor, colR);
170
+ row(r2c1.padded, r2c2.padded);
171
+
172
+ this._line(chalk.cyan(GM));
173
+
174
+ // Row 3: Target | Risk
175
+ const r3c1 = buildCell('Target', '$' + (stats.target || 0).toFixed(2), chalk.green, colL);
176
+ const r3c2 = buildCell('Risk', '$' + (stats.risk || 0).toFixed(2), chalk.red, colR);
177
+ row(r3c1.padded, r3c2.padded);
178
+
179
+ this._line(chalk.cyan(GM));
180
+
181
+ // Row 4: Trades | Server
182
+ const r4c1t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
183
+ const r4c1p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
184
+ const r4c2 = buildCell('Server', stats.connected ? 'ON' : 'OFF', serverColor, colR);
185
+ row(r4c1t + pad(colL - r4c1p.length), r4c2.padded);
186
+
187
+ this._line(chalk.cyan(GM));
188
+
189
+ // Row 5: Latency | Platform
190
+ const r5c1 = buildCell('Latency', `${stats.latency || 0}ms`, latencyColor, colL);
191
+ const r5c2 = buildCell('Platform', stats.platform || 'N/A', chalk.cyan, colR);
192
+ row(r5c1.padded, r5c2.padded);
193
+
194
+ this._line(chalk.cyan(GB));
195
+ }
196
+
197
+ _drawCopyTradingStats(stats, pnlColor, pnlStr, latencyColor, serverColor) {
198
+ const { W } = this;
199
+ const colL = 48, colR = 47;
200
+ const pad = (len) => ' '.repeat(Math.max(0, len));
201
+
138
202
  const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
139
203
  const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
140
204
  const GB = BOX.ML + BOX.H.repeat(colL) + BOX.BM + BOX.H.repeat(colR) + BOX.MR;
141
205
 
142
- // Row builders
143
206
  const row = (c1, c2) => {
144
207
  this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V));
145
208
  };
@@ -147,9 +210,8 @@ class AlgoUI {
147
210
  this._line(chalk.cyan(GT));
148
211
 
149
212
  // Row 1: Lead Account | Follower Account
150
- const leadName = (stats.leadName || stats.accountName || 'N/A').substring(0, 40);
213
+ const leadName = (stats.leadName || 'N/A').substring(0, 40);
151
214
  const followerName = (stats.followerName || 'N/A').substring(0, 40);
152
-
153
215
  const r1c1 = buildCell('Lead', leadName, chalk.cyan, colL);
154
216
  const r1c2 = buildCell('Follower', followerName, chalk.magenta, colR);
155
217
  row(r1c1.padded, r1c2.padded);
@@ -158,8 +220,7 @@ class AlgoUI {
158
220
 
159
221
  // Row 2: Lead Symbol | Follower Symbol
160
222
  const leadSymbol = (stats.leadSymbol || stats.symbol || 'N/A').substring(0, 35);
161
- const followerSymbol = (stats.followerSymbol || 'N/A').substring(0, 35);
162
-
223
+ const followerSymbol = (stats.followerSymbol || stats.symbol || 'N/A').substring(0, 35);
163
224
  const r2c1 = buildCell('Symbol', leadSymbol, chalk.yellow, colL);
164
225
  const r2c2 = buildCell('Symbol', followerSymbol, chalk.yellow, colR);
165
226
  row(r2c1.padded, r2c2.padded);
@@ -167,11 +228,8 @@ class AlgoUI {
167
228
  this._line(chalk.cyan(GM));
168
229
 
169
230
  // Row 3: Lead Qty | Follower Qty
170
- const leadQty = stats.leadQty || '1';
171
- const followerQty = stats.followerQty || '1';
172
-
173
- const r3c1 = buildCell('Qty', leadQty.toString(), chalk.cyan, colL);
174
- const r3c2 = buildCell('Qty', followerQty.toString(), chalk.cyan, colR);
231
+ const r3c1 = buildCell('Qty', (stats.leadQty || '1').toString(), chalk.cyan, colL);
232
+ const r3c2 = buildCell('Qty', (stats.followerQty || '1').toString(), chalk.cyan, colR);
175
233
  row(r3c1.padded, r3c2.padded);
176
234
 
177
235
  this._line(chalk.cyan(GM));