hedgequantx 2.6.163 → 2.7.0

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 +6 -3
  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
@@ -1,8 +1,6 @@
1
1
  /**
2
- * @fileoverview Professional Copy Trading Menu
2
+ * @fileoverview Copy Trading Mode
3
3
  * @module pages/algo/copy-trading
4
- *
5
- * Copy trading configuration and session management
6
4
  */
7
5
 
8
6
  const chalk = require('chalk');
@@ -12,13 +10,7 @@ const readline = require('readline');
12
10
  const { connections } = require('../../services');
13
11
  const { AlgoUI, renderSessionSummary } = require('./ui');
14
12
  const { logger, prompts } = require('../../utils');
15
- const { checkMarketHours } = require('../../services/projectx/market');
16
- const { algoLogger } = require('./logger');
17
- const { CopyEngine } = require('./copy-engine');
18
-
19
- // AI Strategy Supervisor
20
- const aiService = require('../../services/ai');
21
- const StrategySupervisor = require('../../services/ai/strategy-supervisor');
13
+ const { checkMarketHours } = require('../../services/rithmic/market');
22
14
 
23
15
  const log = logger.scope('CopyTrading');
24
16
 
@@ -28,233 +20,175 @@ const log = logger.scope('CopyTrading');
28
20
  const copyTradingMenu = async () => {
29
21
  log.info('Copy Trading menu opened');
30
22
 
23
+ // Check market hours
24
+ const market = checkMarketHours();
25
+ if (!market.isOpen && !market.message.includes('early')) {
26
+ console.log();
27
+ console.log(chalk.red(` ${market.message}`));
28
+ console.log(chalk.gray(' Algo trading is only available when market is open'));
29
+ console.log();
30
+ await prompts.waitForEnter();
31
+ return;
32
+ }
33
+
31
34
  const allConns = connections.getAll();
32
35
 
33
- if (allConns.length === 0) {
36
+ if (allConns.length < 2) {
34
37
  console.log();
35
- console.log(chalk.yellow(' No connections found'));
36
- console.log(chalk.gray(' Connect to a PropFirm first'));
38
+ console.log(chalk.yellow(` Copy Trading requires 2 connected accounts (found: ${allConns.length})`));
39
+ console.log(chalk.gray(' Connect to another PropFirm first'));
37
40
  console.log();
38
41
  await prompts.waitForEnter();
39
42
  return;
40
43
  }
41
44
 
42
45
  console.log();
43
- console.log(chalk.yellow.bold(' COPY TRADING - Professional Mode'));
46
+ console.log(chalk.yellow.bold(' Copy Trading Setup'));
44
47
  console.log();
45
48
 
46
49
  // Fetch all accounts
47
- const spinner = ora({ text: 'FETCHING ACCOUNTS...', color: 'yellow' }).start();
50
+ const spinner = ora({ text: 'Fetching accounts...', color: 'yellow' }).start();
48
51
  const allAccounts = await fetchAllAccounts(allConns);
49
52
 
50
53
  if (allAccounts.length < 2) {
51
- spinner.fail('NEED AT LEAST 2 ACTIVE ACCOUNTS');
52
- console.log(chalk.gray(' Copy Trading requires a lead + at least one follower'));
54
+ spinner.fail('Need at least 2 active accounts');
53
55
  await prompts.waitForEnter();
54
56
  return;
55
57
  }
56
58
 
57
- spinner.succeed(`Found ${allAccounts.length} accounts across ${allConns.length} connection(s)`);
59
+ spinner.succeed(`Found ${allAccounts.length} active accounts`);
58
60
 
59
61
  // Step 1: Select Lead Account
60
- console.log();
61
- console.log(chalk.cyan.bold(' Step 1: Select LEAD Account (source)'));
62
- const leadIdx = await selectAccount('LEAD ACCOUNT:', allAccounts, []);
62
+ console.log(chalk.cyan(' Step 1: Select LEAD Account'));
63
+ const leadIdx = await selectAccount('Lead Account:', allAccounts, -1);
63
64
  if (leadIdx === null || leadIdx === -1) return;
64
65
  const lead = allAccounts[leadIdx];
65
66
 
66
- // Step 2: Select Follower Accounts (multiple)
67
+ // Step 2: Select Follower Account
67
68
  console.log();
68
- console.log(chalk.cyan.bold(' Step 2: Select FOLLOWER Account(s)'));
69
- console.log(chalk.gray(' Select accounts to copy trades to'));
70
-
71
- const followers = [];
72
- let selectingFollowers = true;
73
- const excludeIndices = [leadIdx];
74
-
75
- while (selectingFollowers && excludeIndices.length < allAccounts.length) {
76
- const followerIdx = await selectAccount(
77
- followers.length === 0 ? 'FOLLOWER ACCOUNT:' : 'ADD ANOTHER FOLLOWER:',
78
- allAccounts,
79
- excludeIndices,
80
- followers.length > 0
81
- );
82
-
83
- if (followerIdx === null || followerIdx === -1) {
84
- if (followers.length === 0) return;
85
- selectingFollowers = false;
86
- } else if (followerIdx === -2) {
87
- selectingFollowers = false;
88
- } else {
89
- followers.push(allAccounts[followerIdx]);
90
- excludeIndices.push(followerIdx);
91
- console.log(chalk.green(` Added: ${allAccounts[followerIdx].propfirm}`));
92
- }
93
- }
94
-
95
- if (followers.length === 0) {
96
- console.log(chalk.red(' No followers selected'));
97
- await prompts.waitForEnter();
98
- return;
99
- }
69
+ console.log(chalk.cyan(' Step 2: Select FOLLOWER Account'));
70
+ const followerIdx = await selectAccount('Follower Account:', allAccounts, leadIdx);
71
+ if (followerIdx === null || followerIdx === -1) return;
72
+ const follower = allAccounts[followerIdx];
100
73
 
101
74
  // Step 3: Select Symbol
102
75
  console.log();
103
- console.log(chalk.cyan.bold(' Step 3: Select Trading Symbol'));
76
+ console.log(chalk.cyan(' Step 3: Select Trading Symbol'));
104
77
  const symbol = await selectSymbol(lead.service);
105
78
  if (!symbol) return;
106
79
 
107
- // Step 4: Configure contracts for each account
80
+ // Step 4: Configure Parameters
108
81
  console.log();
109
- console.log(chalk.cyan.bold(' Step 4: Configure Contract Sizes'));
110
-
111
- const leadContracts = await prompts.numberInput(`${lead.propfirm} (LEAD) contracts:`, 1, 1, 10);
82
+ console.log(chalk.cyan(' Step 4: Configure Parameters'));
83
+
84
+ const leadContracts = await prompts.numberInput('Lead contracts:', 1, 1, 10);
112
85
  if (leadContracts === null) return;
113
- lead.contracts = leadContracts;
114
-
115
- for (const follower of followers) {
116
- const contracts = await prompts.numberInput(`${follower.propfirm} contracts:`, leadContracts, 1, 10);
117
- if (contracts === null) return;
118
- follower.contracts = contracts;
119
- }
120
86
 
121
- // Step 5: Risk parameters
122
- console.log();
123
- console.log(chalk.cyan.bold(' Step 5: Risk Parameters'));
124
-
125
- const dailyTarget = await prompts.numberInput('Daily target ($):', 1000, 1, 10000);
87
+ const followerContracts = await prompts.numberInput('Follower contracts:', leadContracts, 1, 10);
88
+ if (followerContracts === null) return;
89
+
90
+ const dailyTarget = await prompts.numberInput('Daily target ($):', 400, 1, 10000);
126
91
  if (dailyTarget === null) return;
127
92
 
128
- const maxRisk = await prompts.numberInput('Max risk ($):', 500, 1, 5000);
93
+ const maxRisk = await prompts.numberInput('Max risk ($):', 200, 1, 5000);
129
94
  if (maxRisk === null) return;
130
95
 
131
- // Step 6: Privacy
132
- const showNames = await prompts.selectOption('ACCOUNT NAMES:', [
133
- { label: 'HIDE ACCOUNT NAMES', value: false },
134
- { label: 'SHOW ACCOUNT NAMES', value: true },
96
+ // Step 5: Privacy
97
+ const showNames = await prompts.selectOption('Account names:', [
98
+ { label: 'Hide account names', value: false },
99
+ { label: 'Show account names', value: true },
135
100
  ]);
136
101
  if (showNames === null) return;
137
102
 
138
- // Step 7: AI Agents option
139
- const aiAgents = aiService.getAgents();
140
- let enableAI = false;
141
-
142
- if (aiAgents.length > 0) {
143
- console.log();
144
- console.log(chalk.magenta(` ${aiAgents.length} AI AGENT(S) AVAILABLE:`));
145
- aiAgents.forEach((agent, i) => {
146
- const modelInfo = agent.model ? chalk.gray(` (${agent.model})`) : '';
147
- console.log(chalk.white(` ${i + 1}. ${agent.name}${modelInfo}`));
148
- });
149
- console.log();
150
-
151
- enableAI = await prompts.confirmPrompt('ACTIVATE AI MODELS?', true);
152
- if (enableAI === null) return;
153
-
154
- if (enableAI) {
155
- const mode = aiAgents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL';
156
- console.log(chalk.green(` AI MODE: ${mode} (${aiAgents.length} agent${aiAgents.length > 1 ? 's' : ''})`));
157
- } else {
158
- console.log(chalk.gray(' AI AGENTS DISABLED FOR THIS SESSION'));
159
- }
160
- }
161
-
162
- // Summary
103
+ // Confirm
163
104
  console.log();
164
- console.log(chalk.white.bold(' ═══════════════════════════════════════'));
165
- console.log(chalk.white.bold(' COPY TRADING CONFIGURATION'));
166
- console.log(chalk.white.bold(' ═══════════════════════════════════════'));
105
+ console.log(chalk.white(' Summary:'));
167
106
  console.log(chalk.cyan(` Symbol: ${symbol.name}`));
168
- console.log(chalk.cyan(` Lead: ${lead.propfirm} (${leadContracts} contracts)`));
169
- console.log(chalk.cyan(` Followers: ${followers.length}`));
170
- followers.forEach(f => {
171
- console.log(chalk.gray(` → ${f.propfirm} (${f.contracts} contracts)`));
172
- });
107
+ console.log(chalk.cyan(` Lead: ${lead.propfirm} x${leadContracts}`));
108
+ console.log(chalk.cyan(` Follower: ${follower.propfirm} x${followerContracts}`));
173
109
  console.log(chalk.cyan(` Target: $${dailyTarget} | Risk: $${maxRisk}`));
174
- console.log(chalk.white.bold(' ═══════════════════════════════════════'));
175
110
  console.log();
176
111
 
177
- const confirm = await prompts.confirmPrompt('START COPY TRADING?', true);
112
+ const confirm = await prompts.confirmPrompt('Start Copy Trading?', true);
178
113
  if (!confirm) return;
179
114
 
180
115
  // Launch
181
116
  await launchCopyTrading({
182
117
  lead: { ...lead, symbol, contracts: leadContracts },
183
- followers: followers.map(f => ({ ...f, symbol })),
118
+ follower: { ...follower, symbol, contracts: followerContracts },
184
119
  dailyTarget,
185
120
  maxRisk,
186
121
  showNames,
187
- enableAI,
188
122
  });
189
123
  };
190
124
 
191
125
  /**
192
126
  * Fetch all active accounts from connections
127
+ * @param {Array} allConns - All connections
128
+ * @returns {Promise<Array>}
193
129
  */
194
130
  const fetchAllAccounts = async (allConns) => {
195
131
  const allAccounts = [];
196
132
 
197
- const promises = allConns.map(async (conn) => {
133
+ for (const conn of allConns) {
198
134
  try {
199
135
  const result = await conn.service.getTradingAccounts();
200
136
  if (result.success && result.accounts) {
201
- return result.accounts
202
- .filter(a => a.status === 0)
203
- .map(acc => ({
137
+ const active = result.accounts.filter(a => a.status === 0);
138
+ for (const acc of active) {
139
+ allAccounts.push({
204
140
  account: acc,
205
141
  service: conn.service,
206
142
  propfirm: conn.propfirm,
207
143
  type: conn.type,
208
- }));
144
+ });
145
+ }
209
146
  }
210
147
  } catch (err) {
211
148
  log.warn('Failed to get accounts', { type: conn.type, error: err.message });
212
149
  }
213
- return [];
214
- });
215
-
216
- const results = await Promise.all(promises);
217
- results.forEach(accounts => allAccounts.push(...accounts));
150
+ }
218
151
 
219
152
  return allAccounts;
220
153
  };
221
154
 
222
155
  /**
223
156
  * Select account from list
157
+ * @param {string} message - Prompt message
158
+ * @param {Array} accounts - Available accounts
159
+ * @param {number} excludeIdx - Index to exclude
160
+ * @returns {Promise<number|null>}
224
161
  */
225
- const selectAccount = async (message, accounts, excludeIndices = [], allowDone = false) => {
162
+ const selectAccount = async (message, accounts, excludeIdx) => {
226
163
  const options = accounts
227
164
  .map((a, i) => ({ a, i }))
228
- .filter(x => !excludeIndices.includes(x.i))
165
+ .filter(x => x.i !== excludeIdx)
229
166
  .map(x => {
230
167
  const acc = x.a.account;
231
- const balance = acc.balance !== null && acc.balance !== undefined
232
- ? ` ($${acc.balance.toLocaleString()})`
233
- : '';
234
- const platform = x.a.type === 'rithmic' ? ' [Rithmic]' : '';
168
+ const balance = acc.balance !== null ? ` ($${acc.balance.toLocaleString()})` : '';
235
169
  return {
236
- label: `${x.a.propfirm} - ${acc.accountName || acc.rithmicAccountId || acc.accountId}${balance}${platform}`,
170
+ label: `${x.a.propfirm} - ${acc.accountName || acc.rithmicAccountId || acc.name || acc.accountId}${balance}`,
237
171
  value: x.i,
238
172
  };
239
173
  });
240
174
 
241
- if (allowDone) {
242
- options.push({ label: chalk.green('✓ DONE ADDING FOLLOWERS'), value: -2 });
243
- }
244
- options.push({ label: chalk.gray('< CANCEL'), value: -1 });
245
-
175
+ options.push({ label: '< Cancel', value: -1 });
246
176
  return prompts.selectOption(message, options);
247
177
  };
248
178
 
249
179
  /**
250
180
  * Select trading symbol
181
+ * @param {Object} service - Service instance
182
+ * @returns {Promise<Object|null>}
251
183
  */
252
184
  const selectSymbol = async (service) => {
253
- const spinner = ora({ text: 'LOADING SYMBOLS...', color: 'yellow' }).start();
185
+ const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
254
186
 
255
187
  try {
188
+ // Try Rithmic API first for consistency
256
189
  let contracts = await getContractsFromAPI();
257
190
 
191
+ // Fallback to service
258
192
  if (!contracts && typeof service.getContracts === 'function') {
259
193
  const result = await service.getContracts();
260
194
  if (result.success && result.contracts?.length > 0) {
@@ -263,34 +197,37 @@ const selectSymbol = async (service) => {
263
197
  }
264
198
 
265
199
  if (!contracts || !contracts.length) {
266
- spinner.fail('NO CONTRACTS AVAILABLE');
200
+ spinner.fail('No contracts available');
267
201
  await prompts.waitForEnter();
268
202
  return null;
269
203
  }
270
204
 
271
205
  spinner.succeed(`Found ${contracts.length} contracts`);
272
206
 
273
- // Sort by popular symbols first
274
- const popular = ['ES', 'NQ', 'MES', 'MNQ', 'RTY', 'YM', 'CL', 'GC'];
275
- contracts.sort((a, b) => {
276
- const aIdx = popular.findIndex(p => (a.name || '').startsWith(p));
277
- const bIdx = popular.findIndex(p => (b.name || '').startsWith(p));
278
- if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx;
279
- if (aIdx !== -1) return -1;
280
- if (bIdx !== -1) return 1;
281
- return (a.name || '').localeCompare(b.name || '');
282
- });
207
+ // Build options from RAW API data - no static mapping
208
+ const options = [];
209
+ let currentGroup = null;
210
+
211
+ for (const c of contracts) {
212
+ // Use RAW API field: contractGroup
213
+ if (c.contractGroup && c.contractGroup !== currentGroup) {
214
+ currentGroup = c.contractGroup;
215
+ options.push({
216
+ label: chalk.cyan.bold(`── ${currentGroup} ──`),
217
+ value: null,
218
+ disabled: true,
219
+ });
220
+ }
283
221
 
284
- const options = contracts.slice(0, 30).map(c => {
285
- const name = c.name || c.symbol || c.baseSymbol;
286
- const desc = c.description || '';
287
- const label = desc ? `${name} - ${desc}` : name;
288
- return { label, value: c };
289
- });
222
+ // Use RAW API fields: name (symbol), description (full name), exchange
223
+ const label = ` ${c.name} - ${c.description} (${c.exchange})`;
224
+ options.push({ label, value: c });
225
+ }
290
226
 
291
- options.push({ label: chalk.gray('< CANCEL'), value: null });
227
+ options.push({ label: '', value: null, disabled: true });
228
+ options.push({ label: chalk.gray('< Cancel'), value: null });
292
229
 
293
- return prompts.selectOption('TRADING SYMBOL:', options);
230
+ return prompts.selectOption('Trading Symbol:', options);
294
231
  } catch (err) {
295
232
  spinner.fail(`Error loading contracts: ${err.message}`);
296
233
  await prompts.waitForEnter();
@@ -299,15 +236,17 @@ const selectSymbol = async (service) => {
299
236
  };
300
237
 
301
238
  /**
302
- * Get contracts from ProjectX API
239
+ * Get contracts from Rithmic API - RAW data only
240
+ * @returns {Promise<Array|null>}
303
241
  */
304
242
  const getContractsFromAPI = async () => {
305
243
  const allConns = connections.getAll();
306
- const projectxConn = allConns.find(c => c.type === 'projectx');
244
+ const rithmicConn = allConns.find(c => c.type === 'rithmic');
307
245
 
308
- if (projectxConn && typeof projectxConn.service.getContracts === 'function') {
309
- const result = await projectxConn.service.getContracts();
246
+ if (rithmicConn && typeof rithmicConn.service.getContracts === 'function') {
247
+ const result = await rithmicConn.service.getContracts();
310
248
  if (result.success && result.contracts?.length > 0) {
249
+ // Return RAW API data - no mapping
311
250
  return result.contracts;
312
251
  }
313
252
  }
@@ -317,128 +256,159 @@ const getContractsFromAPI = async () => {
317
256
 
318
257
  /**
319
258
  * Launch Copy Trading session
259
+ * @param {Object} config - Session configuration
320
260
  */
321
261
  const launchCopyTrading = async (config) => {
322
- const { lead, followers, dailyTarget, maxRisk, showNames, enableAI } = config;
262
+ const { lead, follower, dailyTarget, maxRisk, showNames } = config;
323
263
 
324
- const leadName = showNames
325
- ? (lead.account.accountName || lead.account.accountId)
326
- : 'Lead *****';
327
-
328
- const ui = new AlgoUI({ subtitle: 'COPY TRADING PRO', mode: 'copy-trading' });
264
+ // Account names (masked for privacy)
265
+ const leadName = showNames ? lead.account.accountId : 'HQX Lead *****';
266
+ const followerName = showNames ? follower.account.accountId : 'HQX Follower *****';
267
+
268
+ const ui = new AlgoUI({ subtitle: 'HQX Copy Trading', mode: 'copy-trading' });
329
269
 
330
270
  const stats = {
331
271
  leadName,
332
- followerCount: followers.length,
272
+ followerName,
333
273
  leadSymbol: lead.symbol.name,
274
+ followerSymbol: follower.symbol.name,
334
275
  leadQty: lead.contracts,
276
+ followerQty: follower.contracts,
335
277
  target: dailyTarget,
336
278
  risk: maxRisk,
337
279
  pnl: 0,
338
- sessionPnl: 0,
339
280
  trades: 0,
340
281
  wins: 0,
341
282
  losses: 0,
342
283
  latency: 0,
343
284
  connected: false,
344
- platform: lead.type === 'rithmic' ? 'Rithmic' : 'ProjectX',
345
- startTime: Date.now(),
346
- aiSupervision: false,
347
- aiMode: null,
348
- agentCount: 0
285
+ platform: lead.account.platform || 'Rithmic',
349
286
  };
350
287
 
351
- // Initialize AI Supervisor
352
- if (enableAI) {
353
- const aiAgents = aiService.getAgents();
354
- stats.agentCount = aiAgents.length;
355
- if (aiAgents.length > 0) {
356
- const supervisorResult = StrategySupervisor.initialize(null, aiAgents, lead.service, lead.account.accountId);
357
- stats.aiSupervision = supervisorResult.success;
358
- stats.aiMode = supervisorResult.mode;
288
+ let running = true;
289
+ let stopReason = null;
290
+
291
+ // Measure API latency (CLI <-> API)
292
+ const measureLatency = async () => {
293
+ try {
294
+ const start = Date.now();
295
+ await lead.service.getPositions(lead.account.accountId);
296
+ stats.latency = Date.now() - start;
297
+ } catch (e) {
298
+ stats.latency = 0;
359
299
  }
360
- }
361
-
362
- // Startup logs
363
- const market = checkMarketHours();
364
- const sessionName = market.session || 'AMERICAN';
365
- const etTime = new Date().toLocaleTimeString('en-US', {
366
- hour: '2-digit', minute: '2-digit', timeZone: 'America/New_York'
367
- });
368
-
369
- algoLogger.connectingToEngine(ui, lead.account.accountId);
370
- algoLogger.engineStarting(ui, stats.platform, dailyTarget, maxRisk);
371
- algoLogger.marketOpen(ui, sessionName.toUpperCase(), etTime);
372
- algoLogger.info(ui, 'COPY MODE', `Lead: ${lead.propfirm} -> ${followers.length} follower(s)`);
373
-
374
- if (stats.aiSupervision) {
375
- const aiAgents = aiService.getAgents();
376
- algoLogger.info(ui, 'AI SUPERVISION', `${aiAgents.length} agent(s) - LEARNING ACTIVE`);
377
- }
300
+ };
378
301
 
379
- // Create copy engine
380
- const engine = new CopyEngine({
381
- lead,
382
- followers,
383
- symbol: lead.symbol,
384
- dailyTarget,
385
- maxRisk,
386
- ui,
387
- stats
388
- });
302
+ // Local copy trading - no external server needed
303
+ ui.addLog('info', `Starting copy trading on ${stats.platform}...`);
304
+ ui.addLog('info', `Lead: ${stats.leadName} -> Follower: ${stats.followerName}`);
305
+ ui.addLog('info', `Symbol: ${stats.symbol} | Target: $${dailyTarget} | Risk: $${maxRisk}`);
306
+ stats.connected = true;
307
+
308
+ // Track lead positions and copy to follower
309
+ let lastLeadPositions = [];
310
+
311
+ const pollAndCopy = async () => {
312
+ try {
313
+ // Get lead positions
314
+ const leadResult = await lead.service.getPositions(lead.account.accountId);
315
+ if (!leadResult.success) return;
316
+
317
+ const currentPositions = leadResult.positions || [];
318
+
319
+ // Detect new positions on lead
320
+ for (const pos of currentPositions) {
321
+ const existing = lastLeadPositions.find(p => p.contractId === pos.contractId);
322
+ if (!existing && pos.quantity !== 0) {
323
+ // New position opened - copy to follower
324
+ ui.addLog('trade', `Lead opened: ${pos.quantity > 0 ? 'LONG' : 'SHORT'} ${Math.abs(pos.quantity)}x ${pos.symbol || pos.contractId}`);
325
+ // TODO: Place order on follower account
326
+ }
327
+ }
328
+
329
+ // Detect closed positions
330
+ for (const oldPos of lastLeadPositions) {
331
+ const stillOpen = currentPositions.find(p => p.contractId === oldPos.contractId);
332
+ if (!stillOpen || stillOpen.quantity === 0) {
333
+ ui.addLog('info', `Lead closed: ${oldPos.symbol || oldPos.contractId}`);
334
+ // TODO: Close position on follower account
335
+ }
336
+ }
337
+
338
+ lastLeadPositions = currentPositions;
339
+
340
+ // Update P&L from lead
341
+ const leadPnL = currentPositions.reduce((sum, p) => sum + (p.profitAndLoss || 0), 0);
342
+ if (leadPnL !== stats.pnl) {
343
+ const diff = leadPnL - stats.pnl;
344
+ if (Math.abs(diff) > 0.01 && stats.pnl !== 0) {
345
+ stats.trades++;
346
+ if (diff >= 0) stats.wins++;
347
+ else stats.losses++;
348
+ }
349
+ stats.pnl = leadPnL;
350
+ }
351
+
352
+ // Check target/risk limits
353
+ if (stats.pnl >= dailyTarget) {
354
+ stopReason = 'target';
355
+ running = false;
356
+ ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
357
+ } else if (stats.pnl <= -maxRisk) {
358
+ stopReason = 'risk';
359
+ running = false;
360
+ ui.addLog('error', `MAX RISK HIT! -$${Math.abs(stats.pnl).toFixed(2)}`);
361
+ }
362
+ } catch (e) {
363
+ // Silent fail - will retry
364
+ }
365
+ };
389
366
 
390
367
  // UI refresh loop
391
368
  const refreshInterval = setInterval(() => {
392
- if (engine.running) ui.render(stats);
369
+ if (running) ui.render(stats);
393
370
  }, 250);
371
+
372
+ // Measure API latency every 5 seconds
373
+ measureLatency(); // Initial measurement
374
+ const latencyInterval = setInterval(() => { if (running) measureLatency(); }, 5000);
375
+
376
+ // Poll and copy every 2 seconds
377
+ pollAndCopy(); // Initial poll
378
+ const copyInterval = setInterval(() => { if (running) pollAndCopy(); }, 2000);
394
379
 
395
380
  // Keyboard handling
396
381
  const cleanupKeys = setupKeyboardHandler(() => {
397
- engine.stop('manual');
382
+ running = false;
383
+ stopReason = 'manual';
398
384
  });
399
385
 
400
- // Start engine
401
- algoLogger.dataConnected(ui, 'API');
402
- algoLogger.algoOperational(ui, stats.platform);
403
-
404
- const stopReason = await engine.start();
386
+ // Wait for stop
387
+ await new Promise((resolve) => {
388
+ const check = setInterval(() => {
389
+ if (!running) {
390
+ clearInterval(check);
391
+ resolve();
392
+ }
393
+ }, 100);
394
+ });
405
395
 
406
396
  // Cleanup
407
397
  clearInterval(refreshInterval);
398
+ clearInterval(latencyInterval);
399
+ clearInterval(copyInterval);
408
400
  if (cleanupKeys) cleanupKeys();
409
-
410
- // Stop AI Supervisor
411
- if (stats.aiSupervision) {
412
- const aiSummary = StrategySupervisor.stop();
413
- stats.aiLearning = {
414
- optimizations: aiSummary.optimizationsApplied || 0,
415
- patternsLearned: (aiSummary.winningPatterns || 0) + (aiSummary.losingPatterns || 0)
416
- };
417
- }
418
-
419
- // Duration
420
- const durationMs = Date.now() - stats.startTime;
421
- const hours = Math.floor(durationMs / 3600000);
422
- const minutes = Math.floor((durationMs % 3600000) / 60000);
423
- const seconds = Math.floor((durationMs % 60000) / 1000);
424
- stats.duration = hours > 0
425
- ? `${hours}h ${minutes}m ${seconds}s`
426
- : minutes > 0
427
- ? `${minutes}m ${seconds}s`
428
- : `${seconds}s`;
429
-
430
- // Close log file
431
- try { ui.closeLog(stats); } catch {}
432
-
433
401
  ui.cleanup();
434
402
 
435
- // Summary
403
+ // Show summary
436
404
  renderSessionSummary(stats, stopReason);
437
405
  await prompts.waitForEnter();
438
406
  };
439
407
 
440
408
  /**
441
409
  * Setup keyboard handler
410
+ * @param {Function} onStop - Stop callback
411
+ * @returns {Function|null} Cleanup function
442
412
  */
443
413
  const setupKeyboardHandler = (onStop) => {
444
414
  if (!process.stdin.isTTY) return null;