hedgequantx 2.6.162 → 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 (138) 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 +235 -0
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +60 -291
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -361
  71. package/src/services/rithmic/protobuf.js +5 -195
  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/index.js +0 -526
  106. package/src/services/ai/proxy-install.js +0 -249
  107. package/src/services/ai/proxy-manager.js +0 -494
  108. package/src/services/ai/proxy-remote.js +0 -161
  109. package/src/services/ai/strategy-supervisor.js +0 -1312
  110. package/src/services/ai/supervisor-data.js +0 -195
  111. package/src/services/ai/supervisor-optimize.js +0 -215
  112. package/src/services/ai/supervisor-sync.js +0 -178
  113. package/src/services/ai/supervisor-utils.js +0 -158
  114. package/src/services/ai/supervisor.js +0 -484
  115. package/src/services/ai/validation.js +0 -250
  116. package/src/services/hqx-server-events.js +0 -110
  117. package/src/services/hqx-server-handlers.js +0 -217
  118. package/src/services/hqx-server-latency.js +0 -136
  119. package/src/services/hqx-server.js +0 -403
  120. package/src/services/position-constants.js +0 -28
  121. package/src/services/position-manager.js +0 -528
  122. package/src/services/position-momentum.js +0 -206
  123. package/src/services/projectx/accounts.js +0 -142
  124. package/src/services/projectx/index.js +0 -443
  125. package/src/services/projectx/market.js +0 -172
  126. package/src/services/projectx/stats.js +0 -110
  127. package/src/services/projectx/trading.js +0 -180
  128. package/src/services/rithmic/latency-tracker.js +0 -182
  129. package/src/services/rithmic/market-data.js +0 -549
  130. package/src/services/rithmic/specs.js +0 -146
  131. package/src/services/rithmic/trade-history.js +0 -254
  132. package/src/services/session-history.js +0 -475
  133. package/src/services/strategy/hft-tick.js +0 -507
  134. package/src/services/strategy/recovery-math.js +0 -402
  135. package/src/services/tradovate/constants.js +0 -109
  136. package/src/services/tradovate/index.js +0 -505
  137. package/src/services/tradovate/market.js +0 -47
  138. package/src/services/tradovate/websocket.js +0 -97
@@ -1,122 +1,59 @@
1
1
  /**
2
- * Algo Trading - Simple Menu
2
+ * Algo Trading - Main Menu
3
3
  */
4
4
 
5
5
  const chalk = require('chalk');
6
- const { getLogoWidth, drawBoxHeaderContinue, drawBoxFooter, displayBanner, prepareStdin } = require('../../ui');
7
- const { prompts } = require('../../utils');
8
- const { checkMarketHours } = require('../../services/projectx/market');
6
+ const { getSeparator } = require('../../ui');
7
+ const { logger, prompts } = require('../../utils');
8
+
9
+ const log = logger.scope('AlgoMenu');
9
10
 
10
11
  const { oneAccountMenu } = require('./one-account');
11
12
  const { copyTradingMenu } = require('./copy-trading');
12
- const { customStrategyMenu } = require('./custom-strategy');
13
- const aiService = require('../../services/ai');
14
13
 
15
14
  /**
16
- * Algo Trading Menu - Simplified
15
+ * Algo Trading Menu
17
16
  */
18
17
  const algoTradingMenu = async (service) => {
19
- // Ensure stdin is ready
20
- prepareStdin();
21
-
22
- // Check market hours first - block if closed
23
- const market = checkMarketHours();
24
- if (!market.isOpen) {
25
- console.clear();
26
- displayBanner();
27
-
28
- const boxWidth = getLogoWidth();
29
- const W = boxWidth - 2;
30
-
31
- drawBoxHeaderContinue('ALGO TRADING', boxWidth);
32
-
33
- // Centered error message
34
- const errorMsg = chalk.red('MARKET CLOSED');
35
- const errorLen = 'MARKET CLOSED'.length;
36
- const errorPad = Math.floor((W - errorLen) / 2);
37
- console.log(chalk.cyan('║') + ' '.repeat(errorPad) + errorMsg + ' '.repeat(W - errorLen - errorPad) + chalk.cyan('║'));
38
-
39
- const reason = market.message || 'Market is currently closed';
40
- const reasonLen = reason.length;
41
- const reasonPad = Math.floor((W - reasonLen) / 2);
42
- console.log(chalk.cyan('║') + ' '.repeat(reasonPad) + chalk.gray(reason) + ' '.repeat(W - reasonLen - reasonPad) + chalk.cyan('║'));
43
-
44
- const unavail = 'ALGO TRADING UNAVAILABLE';
45
- const unavailLen = unavail.length;
46
- const unavailPad = Math.floor((W - unavailLen) / 2);
47
- console.log(chalk.cyan('║') + ' '.repeat(unavailPad) + chalk.yellow(unavail) + ' '.repeat(W - unavailLen - unavailPad) + chalk.cyan('║'));
48
-
49
- drawBoxFooter(boxWidth);
18
+ log.info('Algo Trading menu opened');
19
+
20
+ try {
21
+ console.log();
22
+ console.log(chalk.gray(getSeparator()));
23
+ console.log(chalk.yellow.bold(' Algo-Trading'));
24
+ console.log(chalk.gray(getSeparator()));
25
+ console.log();
26
+
27
+ const action = await prompts.selectOption(chalk.yellow('Select Mode:'), [
28
+ { value: 'one_account', label: 'One Account' },
29
+ { value: 'copy_trading', label: 'Copy Trading' },
30
+ { value: 'back', label: '< Back' }
31
+ ]);
32
+
33
+ log.debug('Algo mode selected', { action });
34
+
35
+ if (!action || action === 'back') {
36
+ return 'back';
37
+ }
38
+
39
+ switch (action) {
40
+ case 'one_account':
41
+ log.info('Starting One Account mode');
42
+ await oneAccountMenu(service);
43
+ break;
44
+ case 'copy_trading':
45
+ log.info('Starting Copy Trading mode');
46
+ await copyTradingMenu();
47
+ break;
48
+ }
50
49
 
50
+ return action;
51
+ } catch (err) {
52
+ log.error('Algo menu error:', err.message);
53
+ console.log(chalk.red(` Error: ${err.message}`));
51
54
  await prompts.waitForEnter();
52
55
  return 'back';
53
56
  }
54
-
55
- const boxWidth = getLogoWidth();
56
- const W = boxWidth - 2;
57
-
58
- const makeLine = (content) => {
59
- const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
60
- const padding = W - plainLen;
61
- return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
62
- };
63
-
64
- // Check if AI agent is connected
65
- const agents = aiService.getAgents();
66
- const hasAgent = agents && agents.length > 0;
67
-
68
- console.clear();
69
- displayBanner();
70
- drawBoxHeaderContinue('ALGO TRADING', boxWidth);
71
-
72
- // Menu options - show CUSTOM STRATEGY only if agent connected
73
- let menuText, plainLen;
74
- if (hasAgent) {
75
- menuText = chalk.cyan('[1]') + chalk.yellow(' ONE ACCOUNT ') + chalk.cyan('[2]') + chalk.yellow(' COPY TRADING ') + chalk.cyan('[3]') + chalk.green(' CUSTOM STRATEGY');
76
- plainLen = '[1] ONE ACCOUNT [2] COPY TRADING [3] CUSTOM STRATEGY'.length;
77
- } else {
78
- menuText = chalk.cyan('[1]') + chalk.yellow(' ONE ACCOUNT ') + chalk.cyan('[2]') + chalk.yellow(' COPY TRADING');
79
- plainLen = '[1] ONE ACCOUNT [2] COPY TRADING'.length;
80
- }
81
- const leftPad = Math.floor((W - plainLen) / 2);
82
- const rightPad = W - plainLen - leftPad;
83
- console.log(chalk.cyan('║') + ' '.repeat(leftPad) + menuText + ' '.repeat(rightPad) + chalk.cyan('║'));
84
-
85
- drawBoxFooter(boxWidth);
86
-
87
- const choice = await prompts.textInput(chalk.cyan('SELECT:'));
88
-
89
- if (choice === '1') {
90
- try {
91
- await oneAccountMenu(service);
92
- } catch (err) {
93
- console.log(chalk.red(`\n ERROR: ${err.message}`));
94
- console.log(chalk.gray(` ${err.stack}`));
95
- await prompts.waitForEnter();
96
- }
97
- return algoTradingMenu(service);
98
- } else if (choice === '2') {
99
- try {
100
- await copyTradingMenu();
101
- } catch (err) {
102
- console.log(chalk.red(`\n ERROR: ${err.message}`));
103
- console.log(chalk.gray(` ${err.stack}`));
104
- await prompts.waitForEnter();
105
- }
106
- return algoTradingMenu(service);
107
- } else if (choice === '3' && hasAgent) {
108
- try {
109
- await customStrategyMenu(service);
110
- } catch (err) {
111
- console.log(chalk.red(`\n ERROR: ${err.message}`));
112
- console.log(chalk.gray(` ${err.stack}`));
113
- await prompts.waitForEnter();
114
- }
115
- return algoTradingMenu(service);
116
- }
117
-
118
- // Empty or other = back
119
- return 'back';
120
57
  };
121
58
 
122
59
  module.exports = { algoTradingMenu };
@@ -1,30 +1,43 @@
1
1
  /**
2
2
  * One Account Mode - HQX Ultra Scalping
3
- *
4
- * FAST PATH: Rithmic direct execution (~10-50ms latency)
5
- * SLOW PATH: ProjectX/Tradovate HTTP REST (~50-150ms latency)
6
3
  */
7
4
 
8
5
  const chalk = require('chalk');
9
6
  const ora = require('ora');
7
+ const readline = require('readline');
10
8
 
11
9
  const { connections } = require('../../services');
10
+ const { AlgoUI, renderSessionSummary } = require('./ui');
12
11
  const { prompts } = require('../../utils');
12
+ const { checkMarketHours } = require('../../services/rithmic/market');
13
+
14
+ // Strategy & Market Data
15
+ const { M1 } = require('../../lib/m/s1');
16
+ const { MarketDataFeed } = require('../../lib/data');
17
+
13
18
 
14
- // Shared utilities
15
- const { isRithmicFastPath, MAX_MULTI_SYMBOLS } = require('./algo-utils');
16
- const { selectSymbol, configureAlgo } = require('./algo-config');
17
19
 
18
20
  /**
19
21
  * One Account Menu
20
22
  */
21
23
  const oneAccountMenu = async (service) => {
22
- const spinner = ora({ text: 'FETCHING ACTIVE ACCOUNTS...', color: 'yellow' }).start();
24
+ // Check if market is open (skip early close check - market may still be open)
25
+ const market = checkMarketHours();
26
+ if (!market.isOpen && !market.message.includes('early')) {
27
+ console.log();
28
+ console.log(chalk.red(` ${market.message}`));
29
+ console.log(chalk.gray(' Algo trading is only available when market is open'));
30
+ console.log();
31
+ await prompts.waitForEnter();
32
+ return;
33
+ }
34
+
35
+ const spinner = ora({ text: 'Fetching active accounts...', color: 'yellow' }).start();
23
36
 
24
37
  const allAccounts = await connections.getAllAccounts();
25
38
 
26
39
  if (!allAccounts?.length) {
27
- spinner.fail('NO ACCOUNTS FOUND');
40
+ spinner.fail('No accounts found');
28
41
  await prompts.waitForEnter();
29
42
  return;
30
43
  }
@@ -32,16 +45,16 @@ const oneAccountMenu = async (service) => {
32
45
  const activeAccounts = allAccounts.filter(acc => acc.status === 0);
33
46
 
34
47
  if (!activeAccounts.length) {
35
- spinner.fail('NO ACTIVE ACCOUNTS');
48
+ spinner.fail('No active accounts');
36
49
  await prompts.waitForEnter();
37
50
  return;
38
51
  }
39
52
 
40
- spinner.succeed(`FOUND ${activeAccounts.length} ACTIVE ACCOUNT(S)`);
53
+ spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
41
54
 
42
55
  // Select account - display RAW API fields
43
56
  const options = activeAccounts.map(acc => {
44
- // Use what API returns: accountName for ProjectX, rithmicAccountId for Rithmic
57
+ // Use what API returns: rithmicAccountId or accountName for Rithmic
45
58
  const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
46
59
  const balance = acc.balance !== null && acc.balance !== undefined
47
60
  ? ` - $${acc.balance.toLocaleString()}`
@@ -51,40 +64,380 @@ const oneAccountMenu = async (service) => {
51
64
  value: acc
52
65
  };
53
66
  });
54
- options.push({ label: '< BACK', value: 'back' });
67
+ options.push({ label: '< Back', value: 'back' });
55
68
 
56
- const selectedAccount = await prompts.selectOption('SELECT ACCOUNT:', options);
69
+ const selectedAccount = await prompts.selectOption('Select Account:', options);
57
70
  if (!selectedAccount || selectedAccount === 'back') return;
58
71
 
59
72
  // Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
60
73
  const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
61
74
 
62
- // Check if Rithmic (supports multi-symbol)
63
- const isRithmicAccount = isRithmicFastPath(accountService);
75
+ // Select symbol
76
+ const contract = await selectSymbol(accountService, selectedAccount);
77
+ if (!contract) return;
64
78
 
65
- // Ask for multi-symbol mode (Rithmic only)
66
- let useMultiSymbol = false;
67
- if (isRithmicAccount) {
68
- console.log();
69
- useMultiSymbol = await prompts.confirmPrompt(chalk.cyan(`MULTI-SYMBOL MODE? (up to ${MAX_MULTI_SYMBOLS} symbols)`), false);
70
- if (useMultiSymbol === null) return;
79
+ // Configure algo
80
+ const config = await configureAlgo(selectedAccount, contract);
81
+ if (!config) return;
82
+
83
+ await launchAlgo(accountService, selectedAccount, contract, config);
84
+ };
85
+
86
+ /**
87
+ * Symbol selection - sorted with popular indices first
88
+ */
89
+ const selectSymbol = async (service, account) => {
90
+ const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
91
+
92
+ const contractsResult = await service.getContracts();
93
+ if (!contractsResult.success || !contractsResult.contracts?.length) {
94
+ spinner.fail('Failed to load contracts');
95
+ return null;
71
96
  }
72
97
 
73
- // Select symbol(s)
74
- const contracts = await selectSymbol(accountService, selectedAccount, useMultiSymbol);
75
- if (!contracts) return;
98
+ let contracts = contractsResult.contracts;
76
99
 
77
- // Normalize to array
78
- const contractList = Array.isArray(contracts) ? contracts : [contracts];
100
+ // Sort: Popular indices first (ES, NQ, MES, MNQ, RTY, YM, etc.)
101
+ const popularPrefixes = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
79
102
 
80
- // Configure algo
81
- const config = await configureAlgo(selectedAccount, contractList);
82
- if (!config) return;
103
+ contracts.sort((a, b) => {
104
+ const nameA = a.name || '';
105
+ const nameB = b.name || '';
106
+
107
+ // Check if names start with popular prefixes
108
+ const idxA = popularPrefixes.findIndex(p => nameA.startsWith(p));
109
+ const idxB = popularPrefixes.findIndex(p => nameB.startsWith(p));
110
+
111
+ // Both are popular - sort by popularity order
112
+ if (idxA !== -1 && idxB !== -1) return idxA - idxB;
113
+ // Only A is popular - A first
114
+ if (idxA !== -1) return -1;
115
+ // Only B is popular - B first
116
+ if (idxB !== -1) return 1;
117
+ // Neither - alphabetical
118
+ return nameA.localeCompare(nameB);
119
+ });
120
+
121
+ spinner.succeed(`Found ${contracts.length} contracts`);
122
+
123
+ // Display sorted contracts from API
124
+ const options = contracts.map(c => ({
125
+ label: `${c.name} - ${c.description}`,
126
+ value: c
127
+ }));
128
+
129
+ options.push({ label: chalk.gray('< Back'), value: 'back' });
83
130
 
84
- // Launch algo (unified - works for 1 or multiple symbols)
85
- await launchMultiSymbolRithmic(accountService, selectedAccount, contractList, config);
131
+ const contract = await prompts.selectOption(chalk.yellow('Select Symbol:'), options);
132
+ return contract === 'back' || contract === null ? null : contract;
86
133
  };
87
134
 
88
- const { launchMultiSymbolRithmic } = require('./algo-multi');
135
+ /**
136
+ * Configure algo
137
+ */
138
+ const configureAlgo = async (account, contract) => {
139
+ console.log();
140
+ console.log(chalk.cyan(' Configure Algo Parameters'));
141
+ console.log();
142
+
143
+ const contracts = await prompts.numberInput('Number of contracts:', 1, 1, 10);
144
+ if (contracts === null) return null;
145
+
146
+ const dailyTarget = await prompts.numberInput('Daily target ($):', 200, 1, 10000);
147
+ if (dailyTarget === null) return null;
148
+
149
+ const maxRisk = await prompts.numberInput('Max risk ($):', 100, 1, 5000);
150
+ if (maxRisk === null) return null;
151
+
152
+ const showName = await prompts.confirmPrompt('Show account name?', false);
153
+ if (showName === null) return null;
154
+
155
+ const confirm = await prompts.confirmPrompt('Start algo trading?', true);
156
+ if (!confirm) return null;
157
+
158
+ return { contracts, dailyTarget, maxRisk, showName };
159
+ };
160
+
161
+ /**
162
+ * Launch algo trading - HQX Ultra Scalping Strategy
163
+ * Real-time market data + Strategy signals + Auto order execution
164
+ */
165
+ const launchAlgo = async (service, account, contract, config) => {
166
+ const { contracts, dailyTarget, maxRisk, showName } = config;
167
+
168
+ // Use RAW API fields
169
+ const accountName = showName
170
+ ? (account.accountName || account.rithmicAccountId || account.accountId)
171
+ : 'HQX *****';
172
+ const symbolName = contract.name;
173
+ const contractId = contract.id;
174
+ const connectionType = account.platform || 'Rithmic';
175
+ const tickSize = contract.tickSize || 0.25;
176
+
177
+ const ui = new AlgoUI({ subtitle: 'HQX Ultra Scalping', mode: 'one-account' });
178
+
179
+ const stats = {
180
+ accountName,
181
+ symbol: symbolName,
182
+ qty: contracts,
183
+ target: dailyTarget,
184
+ risk: maxRisk,
185
+ propfirm: account.propfirm || 'Unknown',
186
+ platform: connectionType,
187
+ pnl: 0,
188
+ trades: 0,
189
+ wins: 0,
190
+ losses: 0,
191
+ latency: 0,
192
+ connected: false,
193
+ startTime: Date.now()
194
+ };
195
+
196
+ let running = true;
197
+ let stopReason = null;
198
+ let startingPnL = null;
199
+ let currentPosition = 0; // Current position qty (+ long, - short)
200
+ let pendingOrder = false; // Prevent duplicate orders
201
+ let tickCount = 0;
202
+
203
+ // Initialize Strategy
204
+ const strategy = new M1({ tickSize });
205
+ strategy.initialize(contractId, tickSize);
206
+
207
+ // Initialize Market Data Feed
208
+ const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
209
+
210
+ // Log startup
211
+ ui.addLog('info', `Connection: ${connectionType}`);
212
+ ui.addLog('info', `Account: ${accountName}`);
213
+ ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
214
+ ui.addLog('info', `Target: $${dailyTarget} | Max Risk: $${maxRisk}`);
215
+ ui.addLog('info', 'Connecting to market data...');
216
+
217
+ // Handle strategy signals
218
+ strategy.on('signal', async (signal) => {
219
+ if (!running || pendingOrder || currentPosition !== 0) return;
220
+
221
+ const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
222
+
223
+ ui.addLog('signal', `${direction.toUpperCase()} signal @ ${entry.toFixed(2)} (${(confidence * 100).toFixed(0)}%)`);
224
+
225
+ // Place order via API
226
+ pendingOrder = true;
227
+ try {
228
+ const orderSide = direction === 'long' ? 0 : 1; // 0=Buy, 1=Sell
229
+ const orderResult = await service.placeOrder({
230
+ accountId: account.accountId,
231
+ contractId: contractId,
232
+ type: 2, // Market order
233
+ side: orderSide,
234
+ size: contracts
235
+ });
236
+
237
+ if (orderResult.success) {
238
+ currentPosition = direction === 'long' ? contracts : -contracts;
239
+ stats.trades++;
240
+ ui.addLog('trade', `OPENED ${direction.toUpperCase()} ${contracts}x @ market`);
241
+
242
+ // Place bracket orders (SL/TP)
243
+ if (stopLoss && takeProfit) {
244
+ // Stop Loss
245
+ await service.placeOrder({
246
+ accountId: account.accountId,
247
+ contractId: contractId,
248
+ type: 4, // Stop order
249
+ side: direction === 'long' ? 1 : 0, // Opposite side
250
+ size: contracts,
251
+ stopPrice: stopLoss
252
+ });
253
+
254
+ // Take Profit
255
+ await service.placeOrder({
256
+ accountId: account.accountId,
257
+ contractId: contractId,
258
+ type: 1, // Limit order
259
+ side: direction === 'long' ? 1 : 0,
260
+ size: contracts,
261
+ limitPrice: takeProfit
262
+ });
263
+
264
+ ui.addLog('info', `SL: ${stopLoss.toFixed(2)} | TP: ${takeProfit.toFixed(2)}`);
265
+ }
266
+ } else {
267
+ ui.addLog('error', `Order failed: ${orderResult.error}`);
268
+ }
269
+ } catch (e) {
270
+ ui.addLog('error', `Order error: ${e.message}`);
271
+ }
272
+ pendingOrder = false;
273
+ });
274
+
275
+ // Handle market data ticks
276
+ marketFeed.on('tick', (tick) => {
277
+ tickCount++;
278
+ const latencyStart = Date.now();
279
+
280
+ // Feed tick to strategy
281
+ strategy.processTick({
282
+ contractId: tick.contractId || contractId,
283
+ price: tick.price,
284
+ bid: tick.bid,
285
+ ask: tick.ask,
286
+ volume: tick.volume || 1,
287
+ side: tick.lastTradeSide || 'unknown',
288
+ timestamp: tick.timestamp || Date.now()
289
+ });
290
+
291
+ stats.latency = Date.now() - latencyStart;
292
+
293
+ // Log every 100th tick to show activity
294
+ if (tickCount % 100 === 0) {
295
+ ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
296
+ }
297
+ });
298
+
299
+ marketFeed.on('connected', () => {
300
+ stats.connected = true;
301
+ ui.addLog('success', 'Market data connected!');
302
+ });
303
+
304
+ marketFeed.on('error', (err) => {
305
+ ui.addLog('error', `Market: ${err.message}`);
306
+ });
307
+
308
+ marketFeed.on('disconnected', () => {
309
+ stats.connected = false;
310
+ ui.addLog('error', 'Market data disconnected');
311
+ });
312
+
313
+ // Connect to market data
314
+ try {
315
+ const token = service.token || service.getToken?.();
316
+ const propfirmKey = (account.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
317
+ await marketFeed.connect(token, propfirmKey, contractId);
318
+ await marketFeed.subscribe(symbolName, contractId);
319
+ } catch (e) {
320
+ ui.addLog('error', `Failed to connect: ${e.message}`);
321
+ }
322
+
323
+ // Poll account P&L from API
324
+ const pollPnL = async () => {
325
+ try {
326
+ const accountResult = await service.getTradingAccounts();
327
+ if (accountResult.success && accountResult.accounts) {
328
+ const acc = accountResult.accounts.find(a => a.accountId === account.accountId);
329
+ if (acc && acc.profitAndLoss !== undefined) {
330
+ if (startingPnL === null) startingPnL = acc.profitAndLoss;
331
+ stats.pnl = acc.profitAndLoss - startingPnL;
332
+
333
+ // Record trade result in strategy
334
+ if (stats.pnl !== 0) {
335
+ strategy.recordTradeResult(stats.pnl);
336
+ }
337
+ }
338
+ }
339
+
340
+ // Check positions
341
+ const posResult = await service.getPositions(account.accountId);
342
+ if (posResult.success && posResult.positions) {
343
+ const pos = posResult.positions.find(p => {
344
+ const sym = p.contractId || p.symbol || '';
345
+ return sym.includes(contract.name) || sym.includes(contractId);
346
+ });
347
+
348
+ if (pos && pos.quantity !== 0) {
349
+ currentPosition = pos.quantity;
350
+ const side = pos.quantity > 0 ? 'LONG' : 'SHORT';
351
+ const pnl = pos.profitAndLoss || 0;
352
+
353
+ // Check if position closed (win/loss)
354
+ if (pnl > 0) stats.wins = Math.max(stats.wins, 1);
355
+ else if (pnl < 0) stats.losses = Math.max(stats.losses, 1);
356
+ } else {
357
+ currentPosition = 0;
358
+ }
359
+ }
360
+
361
+ // Check target/risk
362
+ if (stats.pnl >= dailyTarget) {
363
+ stopReason = 'target';
364
+ running = false;
365
+ ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
366
+ } else if (stats.pnl <= -maxRisk) {
367
+ stopReason = 'risk';
368
+ running = false;
369
+ ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
370
+ }
371
+ } catch (e) {
372
+ // Silently handle polling errors
373
+ }
374
+ };
375
+
376
+ // Start polling and UI refresh
377
+ const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
378
+ const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
379
+ pollPnL(); // Initial poll
380
+
381
+ // Keyboard handler
382
+ const setupKeyHandler = () => {
383
+ if (!process.stdin.isTTY) return;
384
+ readline.emitKeypressEvents(process.stdin);
385
+ process.stdin.setRawMode(true);
386
+ process.stdin.resume();
387
+
388
+ const onKey = (str, key) => {
389
+ if (key && (key.name === 'x' || key.name === 'X' || (key.ctrl && key.name === 'c'))) {
390
+ running = false;
391
+ stopReason = 'manual';
392
+ }
393
+ };
394
+ process.stdin.on('keypress', onKey);
395
+ return () => {
396
+ process.stdin.removeListener('keypress', onKey);
397
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
398
+ };
399
+ };
400
+
401
+ const cleanupKeys = setupKeyHandler();
402
+
403
+ // Wait for stop
404
+ await new Promise(resolve => {
405
+ const check = setInterval(() => {
406
+ if (!running) {
407
+ clearInterval(check);
408
+ resolve();
409
+ }
410
+ }, 100);
411
+ });
412
+
413
+ // Cleanup
414
+ clearInterval(refreshInterval);
415
+ clearInterval(pnlInterval);
416
+ await marketFeed.disconnect();
417
+ if (cleanupKeys) cleanupKeys();
418
+ ui.cleanup();
419
+
420
+ if (process.stdin.isTTY) {
421
+ process.stdin.setRawMode(false);
422
+ }
423
+ process.stdin.resume();
424
+
425
+ // Duration
426
+ const durationMs = Date.now() - stats.startTime;
427
+ const hours = Math.floor(durationMs / 3600000);
428
+ const minutes = Math.floor((durationMs % 3600000) / 60000);
429
+ const seconds = Math.floor((durationMs % 60000) / 1000);
430
+ stats.duration = hours > 0
431
+ ? `${hours}h ${minutes}m ${seconds}s`
432
+ : minutes > 0
433
+ ? `${minutes}m ${seconds}s`
434
+ : `${seconds}s`;
435
+
436
+ // Summary
437
+ renderSessionSummary(stats, stopReason);
438
+
439
+ console.log('\n Returning to menu in 3 seconds...');
440
+ await new Promise(resolve => setTimeout(resolve, 3000));
441
+ };
89
442
 
90
- module.exports = { oneAccountMenu, launchMultiSymbolRithmic, selectSymbol, configureAlgo };
443
+ module.exports = { oneAccountMenu };