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.
- package/README.md +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +8 -5
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
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/
|
|
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
|
|
36
|
+
if (allConns.length < 2) {
|
|
34
37
|
console.log();
|
|
35
|
-
console.log(chalk.yellow(
|
|
36
|
-
console.log(chalk.gray(' Connect to
|
|
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('
|
|
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: '
|
|
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('
|
|
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
|
|
59
|
+
spinner.succeed(`Found ${allAccounts.length} active accounts`);
|
|
58
60
|
|
|
59
61
|
// Step 1: Select Lead Account
|
|
60
|
-
console.log();
|
|
61
|
-
|
|
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
|
|
67
|
+
// Step 2: Select Follower Account
|
|
67
68
|
console.log();
|
|
68
|
-
console.log(chalk.cyan
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
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
|
|
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
|
|
80
|
+
// Step 4: Configure Parameters
|
|
108
81
|
console.log();
|
|
109
|
-
console.log(chalk.cyan
|
|
110
|
-
|
|
111
|
-
const leadContracts = await prompts.numberInput(
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 ($):',
|
|
93
|
+
const maxRisk = await prompts.numberInput('Max risk ($):', 200, 1, 5000);
|
|
129
94
|
if (maxRisk === null) return;
|
|
130
95
|
|
|
131
|
-
// Step
|
|
132
|
-
const showNames = await prompts.selectOption('
|
|
133
|
-
{ label: '
|
|
134
|
-
{ label: '
|
|
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
|
-
//
|
|
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
|
|
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}
|
|
169
|
-
console.log(chalk.cyan(`
|
|
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('
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
.
|
|
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
|
-
|
|
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,
|
|
162
|
+
const selectAccount = async (message, accounts, excludeIdx) => {
|
|
226
163
|
const options = accounts
|
|
227
164
|
.map((a, i) => ({ a, i }))
|
|
228
|
-
.filter(x =>
|
|
165
|
+
.filter(x => x.i !== excludeIdx)
|
|
229
166
|
.map(x => {
|
|
230
167
|
const acc = x.a.account;
|
|
231
|
-
const balance = acc.balance !== null
|
|
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}
|
|
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
|
-
|
|
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: '
|
|
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('
|
|
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
|
-
//
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
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:
|
|
227
|
+
options.push({ label: '', value: null, disabled: true });
|
|
228
|
+
options.push({ label: chalk.gray('< Cancel'), value: null });
|
|
292
229
|
|
|
293
|
-
return prompts.selectOption('
|
|
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
|
|
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
|
|
244
|
+
const rithmicConn = allConns.find(c => c.type === 'rithmic');
|
|
307
245
|
|
|
308
|
-
if (
|
|
309
|
-
const result = await
|
|
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,
|
|
262
|
+
const { lead, follower, dailyTarget, maxRisk, showNames } = config;
|
|
323
263
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const ui = new AlgoUI({ subtitle: '
|
|
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
|
-
|
|
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.
|
|
345
|
-
startTime: Date.now(),
|
|
346
|
-
aiSupervision: false,
|
|
347
|
-
aiMode: null,
|
|
348
|
-
agentCount: 0
|
|
285
|
+
platform: lead.account.platform || 'Rithmic',
|
|
349
286
|
};
|
|
350
287
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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 (
|
|
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
|
-
|
|
382
|
+
running = false;
|
|
383
|
+
stopReason = 'manual';
|
|
398
384
|
});
|
|
399
385
|
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
//
|
|
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;
|